aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.h14
-rw-r--r--src/wallet/crypter.cpp154
-rw-r--r--src/wallet/crypter.h56
-rw-r--r--src/wallet/db.cpp172
-rw-r--r--src/wallet/db.h29
-rw-r--r--src/wallet/feebumper.cpp244
-rw-r--r--src/wallet/feebumper.h67
-rw-r--r--src/wallet/fees.cpp14
-rw-r--r--src/wallet/fees.h2
-rw-r--r--src/wallet/init.cpp75
-rw-r--r--src/wallet/rpcdump.cpp458
-rw-r--r--src/wallet/rpcwallet.cpp683
-rw-r--r--src/wallet/rpcwallet.h2
-rw-r--r--src/wallet/test/accounting_tests.cpp26
-rw-r--r--src/wallet/test/crypto_tests.cpp8
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp21
-rw-r--r--src/wallet/test/wallet_test_fixture.h8
-rw-r--r--src/wallet/test/wallet_tests.cpp56
-rw-r--r--src/wallet/wallet.cpp564
-rw-r--r--src/wallet/wallet.h169
-rw-r--r--src/wallet/walletdb.cpp72
-rw-r--r--src/wallet/walletdb.h19
-rw-r--r--src/wallet/walletutil.cpp27
-rw-r--r--src/wallet/walletutil.h14
24 files changed, 1949 insertions, 1005 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index fc0e7c519e..458e770e03 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -1,14 +1,14 @@
-// Copyright (c) 2011-2016 The Bitcoin Core developers
+// Copyright (c) 2011-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.
#ifndef BITCOIN_WALLET_COINCONTROL_H
#define BITCOIN_WALLET_COINCONTROL_H
-#include "policy/feerate.h"
-#include "policy/fees.h"
-#include "primitives/transaction.h"
-#include "wallet/wallet.h"
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <primitives/transaction.h>
+#include <wallet/wallet.h>
#include <boost/optional.hpp>
@@ -16,7 +16,10 @@
class CCoinControl
{
public:
+ //! Custom change destination, if not set an address is generated
CTxDestination destChange;
+ //! Custom change type, ignored if destChange is set, defaults to g_change_type
+ OutputType change_type;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
@@ -40,6 +43,7 @@ public:
void SetNull()
{
destChange = CNoDestination();
+ change_type = g_change_type;
fAllowOtherInputs = false;
fAllowWatchOnly = false;
setSelected.clear();
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index 8db3bfd69c..6ad18721fd 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -1,14 +1,14 @@
-// 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 "crypter.h"
+#include <wallet/crypter.h>
-#include "crypto/aes.h"
-#include "crypto/sha512.h"
-#include "script/script.h"
-#include "script/standard.h"
-#include "util.h"
+#include <crypto/aes.h>
+#include <crypto/sha512.h>
+#include <script/script.h>
+#include <script/standard.h>
+#include <util.h>
#include <string>
#include <vector>
@@ -152,6 +152,15 @@ bool CCryptoKeyStore::SetCrypted()
return true;
}
+bool CCryptoKeyStore::IsLocked() const
+{
+ if (!IsCrypted()) {
+ return false;
+ }
+ LOCK(cs_KeyStore);
+ return vMasterKey.empty();
+}
+
bool CCryptoKeyStore::Lock()
{
if (!SetCrypted())
@@ -206,21 +215,23 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::AddKeyPubKey(key, pubkey);
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::AddKeyPubKey(key, pubkey);
+ }
- if (IsLocked())
- return false;
+ if (IsLocked()) {
+ return false;
+ }
- std::vector<unsigned char> vchCryptedSecret;
- CKeyingMaterial vchSecret(key.begin(), key.end());
- if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret))
- return false;
+ std::vector<unsigned char> vchCryptedSecret;
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) {
+ return false;
+ }
- if (!AddCryptedKey(pubkey, vchCryptedSecret))
- return false;
+ if (!AddCryptedKey(pubkey, vchCryptedSecret)) {
+ return false;
}
return true;
}
@@ -228,72 +239,89 @@ bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
- {
- LOCK(cs_KeyStore);
- if (!SetCrypted())
- return false;
-
- mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ LOCK(cs_KeyStore);
+ if (!SetCrypted()) {
+ return false;
}
+
+ mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ ImplicitlyLearnRelatedKeyScripts(vchPubKey);
return true;
}
+bool CCryptoKeyStore::HaveKey(const CKeyID &address) const
+{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::HaveKey(address);
+ }
+ return mapCryptedKeys.count(address) > 0;
+}
+
bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const
{
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::GetKey(address, keyOut);
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::GetKey(address, keyOut);
+ }
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- const CPubKey &vchPubKey = (*mi).second.first;
- const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
- return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
- }
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
+ {
+ const CPubKey &vchPubKey = (*mi).second.first;
+ const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
+ return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
}
return false;
}
bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted())
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
{
- LOCK(cs_KeyStore);
- if (!IsCrypted())
- return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+ vchPubKeyOut = (*mi).second.first;
+ return true;
+ }
+ // Check for watch-only pubkeys
+ return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+}
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- vchPubKeyOut = (*mi).second.first;
- return true;
- }
- // Check for watch-only pubkeys
- return CBasicKeyStore::GetPubKey(address, vchPubKeyOut);
+std::set<CKeyID> CCryptoKeyStore::GetKeys() const
+{
+ LOCK(cs_KeyStore);
+ if (!IsCrypted()) {
+ return CBasicKeyStore::GetKeys();
}
+ std::set<CKeyID> set_address;
+ for (const auto& mi : mapCryptedKeys) {
+ set_address.insert(mi.first);
+ }
+ return set_address;
}
bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
{
+ LOCK(cs_KeyStore);
+ if (!mapCryptedKeys.empty() || IsCrypted())
+ return false;
+
+ fUseCrypto = true;
+ for (KeyMap::value_type& mKey : mapKeys)
{
- LOCK(cs_KeyStore);
- if (!mapCryptedKeys.empty() || IsCrypted())
+ const CKey &key = mKey.second;
+ CPubKey vchPubKey = key.GetPubKey();
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ std::vector<unsigned char> vchCryptedSecret;
+ if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret))
+ return false;
+ if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
-
- fUseCrypto = true;
- for (KeyMap::value_type& mKey : mapKeys)
- {
- const CKey &key = mKey.second;
- CPubKey vchPubKey = key.GetPubKey();
- CKeyingMaterial vchSecret(key.begin(), key.end());
- std::vector<unsigned char> vchCryptedSecret;
- if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret))
- return false;
- if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
- return false;
- }
- mapKeys.clear();
}
+ mapKeys.clear();
return true;
}
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index eac258b287..f3ae7144b4 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,13 +1,15 @@
-// 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.
#ifndef BITCOIN_WALLET_CRYPTER_H
#define BITCOIN_WALLET_CRYPTER_H
-#include "keystore.h"
-#include "serialize.h"
-#include "support/allocators/secure.h"
+#include <keystore.h>
+#include <serialize.h>
+#include <support/allocators/secure.h>
+
+#include <atomic>
const unsigned int WALLET_CRYPTO_KEY_SIZE = 32;
const unsigned int WALLET_CRYPTO_SALT_SIZE = 8;
@@ -118,7 +120,7 @@ private:
//! if fUseCrypto is true, mapKeys must be empty
//! if fUseCrypto is false, vMasterKey must be empty
- bool fUseCrypto;
+ std::atomic<bool> fUseCrypto;
//! keeps track of whether Unlock has run a thorough check before
bool fDecryptionThoroughlyChecked;
@@ -137,52 +139,16 @@ public:
{
}
- bool IsCrypted() const
- {
- return fUseCrypto;
- }
-
- bool IsLocked() const
- {
- if (!IsCrypted())
- return false;
- bool result;
- {
- LOCK(cs_KeyStore);
- result = vMasterKey.empty();
- }
- return result;
- }
-
+ bool IsCrypted() const { return fUseCrypto; }
+ bool IsLocked() const;
bool Lock();
virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
- bool HaveKey(const CKeyID &address) const override
- {
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return CBasicKeyStore::HaveKey(address);
- }
- return mapCryptedKeys.count(address) > 0;
- }
- return false;
- }
+ bool HaveKey(const CKeyID &address) const override;
bool GetKey(const CKeyID &address, CKey& keyOut) const override;
bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
- std::set<CKeyID> GetKeys() const override
- {
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return CBasicKeyStore::GetKeys();
- }
- std::set<CKeyID> set_address;
- for (const auto& mi : mapCryptedKeys) {
- set_address.insert(mi.first);
- }
- return set_address;
- }
+ std::set<CKeyID> GetKeys() const override;
/**
* Wallet status (encrypted, locked) changed.
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index d66ba48421..23c6279128 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,16 +1,16 @@
// 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 "db.h"
+#include <wallet/db.h>
-#include "addrman.h"
-#include "fs.h"
-#include "hash.h"
-#include "protocol.h"
-#include "util.h"
-#include "utilstrencodings.h"
+#include <addrman.h>
+#include <hash.h>
+#include <protocol.h>
+#include <util.h>
+#include <utilstrencodings.h>
+#include <wallet/walletutil.h>
#include <stdint.h>
@@ -20,6 +20,40 @@
#include <boost/thread.hpp>
+namespace {
+//! Make sure database has a unique fileid within the environment. If it
+//! doesn't, throw an error. BDB caches do not work properly when more than one
+//! open database has the same fileid (values written to one database may show
+//! up in reads to other databases).
+//!
+//! BerkeleyDB generates unique fileids by default
+//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
+//! so bitcoin should never create different databases with the same fileid, but
+//! this error can be triggered if users manually copy database files.
+void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
+{
+ if (env.IsMock()) return;
+
+ u_int8_t fileid[DB_FILE_ID_LEN];
+ int ret = db.get_mpf()->get_fileid(fileid);
+ if (ret != 0) {
+ throw std::runtime_error(strprintf("CDB: Can't open database %s (get_fileid failed with %d)", filename, ret));
+ }
+
+ for (const auto& item : env.mapDb) {
+ u_int8_t item_fileid[DB_FILE_ID_LEN];
+ if (item.second && item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
+ memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
+ const char* item_filename = nullptr;
+ item.second->get_dbname(&item_filename, nullptr);
+ throw std::runtime_error(strprintf("CDB: Can't open database %s (duplicates fileid %s from %s)", filename,
+ HexStr(std::begin(item_fileid), std::end(item_fileid)),
+ item_filename ? item_filename : "(unknown database)"));
+ }
+ }
+}
+} // namespace
+
//
// CDB
//
@@ -41,13 +75,12 @@ void CDBEnv::EnvShutdown()
void CDBEnv::Reset()
{
- delete dbenv;
- dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
+ dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS));
fDbEnvInit = false;
fMockDb = false;
}
-CDBEnv::CDBEnv() : dbenv(nullptr)
+CDBEnv::CDBEnv()
{
Reset();
}
@@ -55,8 +88,6 @@ CDBEnv::CDBEnv() : dbenv(nullptr)
CDBEnv::~CDBEnv()
{
EnvShutdown();
- delete dbenv;
- dbenv = nullptr;
}
void CDBEnv::Close()
@@ -64,7 +95,7 @@ void CDBEnv::Close()
EnvShutdown();
}
-bool CDBEnv::Open(const fs::path& pathIn)
+bool CDBEnv::Open(const fs::path& pathIn, bool retry)
{
if (fDbEnvInit)
return true;
@@ -72,6 +103,11 @@ bool CDBEnv::Open(const fs::path& pathIn)
boost::this_thread::interruption_point();
strPath = pathIn.string();
+ if (!LockDirectory(pathIn, ".walletlock")) {
+ LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
+ return false;
+ }
+
fs::path pathLogDir = pathIn / "database";
TryCreateDirectories(pathLogDir);
fs::path pathErrorFile = pathIn / "db.log";
@@ -103,7 +139,24 @@ bool CDBEnv::Open(const fs::path& pathIn)
S_IRUSR | S_IWUSR);
if (ret != 0) {
dbenv->close(0);
- return error("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ LogPrintf("CDBEnv::Open: Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
+ if (retry) {
+ // try moving the database env out of the way
+ fs::path pathDatabaseBak = pathIn / strprintf("database.%d.bak", GetTime());
+ try {
+ fs::rename(pathLogDir, pathDatabaseBak);
+ LogPrintf("Moved old %s to %s. Retrying.\n", pathLogDir.string(), pathDatabaseBak.string());
+ } catch (const fs::filesystem_error&) {
+ // failure is ok (well, not really, but it's not worse than what we started with)
+ }
+ // try opening it again one more time
+ if (!Open(pathIn, false)) {
+ // if it still fails, it probably means we can't even create the database env
+ return false;
+ }
+ } else {
+ return false;
+ }
}
fDbEnvInit = true;
@@ -148,7 +201,7 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
- Db db(dbenv, 0);
+ Db db(dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
if (result == 0)
return VERIFY_OK;
@@ -191,7 +244,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
}
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
- std::unique_ptr<Db> pdbCopy(new Db(bitdb.dbenv, 0));
+ std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
filename.c_str(), // Filename
"main", // Logical db name
@@ -226,7 +279,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
return fSuccess;
}
-bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr)
+bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
{
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);
@@ -234,35 +287,21 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataD
// Wallet file must be a plain filename without a directory
if (walletFile != fs::basename(walletFile) + fs::extension(walletFile))
{
- errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string());
+ errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), walletFile, walletDir.string());
return false;
}
- if (!bitdb.Open(dataDir))
- {
- // try moving the database env out of the way
- fs::path pathDatabase = dataDir / "database";
- fs::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime());
- try {
- fs::rename(pathDatabase, pathDatabaseBak);
- LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
- } catch (const fs::filesystem_error&) {
- // failure is ok (well, not really, but it's not worse than what we started with)
- }
-
- // try again
- if (!bitdb.Open(dataDir)) {
- // if it still fails, it probably means we can't even create the database env
- errorStr = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
- return false;
- }
+ if (!bitdb.Open(walletDir, true)) {
+ errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
+ return false;
}
+
return true;
}
-bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
+bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
{
- if (fs::exists(dataDir / walletFile))
+ if (fs::exists(walletDir / walletFile))
{
std::string backup_filename;
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename);
@@ -272,7 +311,7 @@ bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& data
" Original %s saved as %s in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."),
- walletFile, backup_filename, dataDir);
+ walletFile, backup_filename, walletDir);
}
if (r == CDBEnv::RECOVER_FAIL)
{
@@ -300,7 +339,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
std::stringstream strDump;
- Db db(dbenv, 0);
+ Db db(dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
if (result == DB_VERIFY_BAD) {
LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data may not be recoverable.\n");
@@ -376,38 +415,37 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
{
LOCK(env->cs_db);
- if (!env->Open(GetDataDir()))
+ if (!env->Open(GetWalletDir()))
throw std::runtime_error("CDB: Failed to open database environment.");
- strFile = strFilename;
- ++env->mapFileUseCount[strFile];
- pdb = env->mapDb[strFile];
+ pdb = env->mapDb[strFilename];
if (pdb == nullptr) {
int ret;
- pdb = new Db(env->dbenv, 0);
+ std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
bool fMockDb = env->IsMock();
if (fMockDb) {
- DbMpoolFile* mpf = pdb->get_mpf();
+ DbMpoolFile* mpf = pdb_temp->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
- if (ret != 0)
- throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFile));
+ if (ret != 0) {
+ throw std::runtime_error(strprintf("CDB: Failed to configure for no temp file backing for database %s", strFilename));
+ }
}
- ret = pdb->open(nullptr, // Txn pointer
- fMockDb ? nullptr : strFile.c_str(), // Filename
- fMockDb ? strFile.c_str() : "main", // Logical db name
- DB_BTREE, // Database type
- nFlags, // Flags
+ ret = pdb_temp->open(nullptr, // Txn pointer
+ fMockDb ? nullptr : strFilename.c_str(), // Filename
+ fMockDb ? strFilename.c_str() : "main", // Logical db name
+ DB_BTREE, // Database type
+ nFlags, // Flags
0);
if (ret != 0) {
- delete pdb;
- pdb = nullptr;
- --env->mapFileUseCount[strFile];
- strFile = "";
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
}
+ CheckUniqueFileid(*env, strFilename, *pdb_temp);
+
+ pdb = pdb_temp.release();
+ env->mapDb[strFilename] = pdb;
if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
@@ -415,9 +453,9 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
WriteVersion(CLIENT_VERSION);
fReadOnly = fTmp;
}
-
- env->mapDb[strFile] = pdb;
}
+ ++env->mapFileUseCount[strFilename];
+ strFile = strFilename;
}
}
@@ -492,7 +530,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
std::string strFileRes = strFile + ".rewrite";
{ // surround usage of db with extra {}
CDB db(dbw, "r");
- Db* pdbCopy = new Db(env->dbenv, 0);
+ std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
@@ -541,13 +579,12 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
} else {
pdbCopy->close(0);
}
- delete pdbCopy;
}
if (fSuccess) {
- Db dbA(env->dbenv, 0);
+ Db dbA(env->dbenv.get(), 0);
if (dbA.remove(strFile.c_str(), nullptr, 0))
fSuccess = false;
- Db dbB(env->dbenv, 0);
+ Db dbB(env->dbenv.get(), 0);
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(), 0))
fSuccess = false;
}
@@ -666,12 +703,17 @@ bool CWalletDBWrapper::Backup(const std::string& strDest)
env->mapFileUseCount.erase(strFile);
// Copy wallet file
- fs::path pathSrc = GetDataDir() / strFile;
+ fs::path pathSrc = GetWalletDir() / strFile;
fs::path pathDest(strDest);
if (fs::is_directory(pathDest))
pathDest /= strFile;
try {
+ if (fs::equivalent(pathSrc, pathDest)) {
+ LogPrintf("cannot backup to wallet source file %s\n", pathDest.string());
+ return false;
+ }
+
fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists);
LogPrintf("copied %s to %s\n", strFile, pathDest.string());
return true;
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 6f3cfe9557..787135e400 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,17 +1,17 @@
// 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.
#ifndef BITCOIN_WALLET_DB_H
#define BITCOIN_WALLET_DB_H
-#include "clientversion.h"
-#include "fs.h"
-#include "serialize.h"
-#include "streams.h"
-#include "sync.h"
-#include "version.h"
+#include <clientversion.h>
+#include <fs.h>
+#include <serialize.h>
+#include <streams.h>
+#include <sync.h>
+#include <version.h>
#include <atomic>
#include <map>
@@ -36,7 +36,7 @@ private:
public:
mutable CCriticalSection cs_db;
- DbEnv *dbenv;
+ std::unique_ptr<DbEnv> dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
@@ -68,7 +68,7 @@ public:
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
- bool Open(const fs::path& path);
+ bool Open(const fs::path& path, bool retry = 0);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
@@ -156,6 +156,9 @@ public:
explicit CDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
~CDB() { Close(); }
+ CDB(const CDB&) = delete;
+ CDB& operator=(const CDB&) = delete;
+
void Flush();
void Close();
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
@@ -164,13 +167,9 @@ public:
ideal to be called periodically */
static bool PeriodicFlush(CWalletDBWrapper& dbw);
/* verifies the database environment */
- static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr);
+ static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr);
/* verifies the database file */
- static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc);
-
-private:
- CDB(const CDB&);
- void operator=(const CDB&);
+ static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc);
public:
template <typename K, typename T>
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 6abd060714..9bfcab54a5 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -2,19 +2,19 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "consensus/validation.h"
-#include "wallet/coincontrol.h"
-#include "wallet/feebumper.h"
-#include "wallet/fees.h"
-#include "wallet/wallet.h"
-#include "policy/fees.h"
-#include "policy/policy.h"
-#include "policy/rbf.h"
-#include "validation.h" //for mempool access
-#include "txmempool.h"
-#include "utilmoneystr.h"
-#include "util.h"
-#include "net.h"
+#include <consensus/validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/feebumper.h>
+#include <wallet/fees.h>
+#include <wallet/wallet.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <validation.h> //for mempool access
+#include <txmempool.h>
+#include <utilmoneystr.h>
+#include <util.h>
+#include <net.h>
// Calculate the size of the transaction assuming all signatures are max size
// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
@@ -23,7 +23,7 @@
// calculation, but we should be able to refactor after priority is removed).
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
-int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWallet)
+static int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet)
{
CMutableTransaction txNew(tx);
std::vector<CInputCoin> vCoins;
@@ -31,11 +31,11 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWal
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
// wallet, with a valid index into the vout array.
for (auto& input : tx.vin) {
- const auto mi = pWallet->mapWallet.find(input.prevout.hash);
- assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ const auto mi = wallet->mapWallet.find(input.prevout.hash);
+ assert(mi != wallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n));
}
- if (!pWallet->DummySignTx(txNew, vCoins)) {
+ if (!wallet->DummySignTx(txNew, vCoins)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
return -1;
@@ -43,103 +43,102 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWal
return GetVirtualTransactionSize(txNew);
}
-bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx) {
- if (pWallet->HasWalletSpend(wtx.GetHash())) {
- vErrors.push_back("Transaction has descendants in the wallet");
- currentResult = BumpFeeResult::INVALID_PARAMETER;
- return false;
+//! Check whether transaction has descendant in wallet or mempool, or has been
+//! mined, or conflicts with a mined transaction. Return a feebumper::Result.
+static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWalletTx& wtx, std::vector<std::string>& errors)
+{
+ if (wallet->HasWalletSpend(wtx.GetHash())) {
+ errors.push_back("Transaction has descendants in the wallet");
+ return feebumper::Result::INVALID_PARAMETER;
}
{
LOCK(mempool.cs);
auto it_mp = mempool.mapTx.find(wtx.GetHash());
if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
- vErrors.push_back("Transaction has descendants in the mempool");
- currentResult = BumpFeeResult::INVALID_PARAMETER;
- return false;
+ errors.push_back("Transaction has descendants in the mempool");
+ return feebumper::Result::INVALID_PARAMETER;
}
}
if (wtx.GetDepthInMainChain() != 0) {
- vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return false;
+ errors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
+ return feebumper::Result::WALLET_ERROR;
}
- return true;
+ return feebumper::Result::OK;
}
-CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee)
- :
- txid(std::move(txidIn)),
- nOldFee(0),
- nNewFee(0)
+namespace feebumper {
+
+bool TransactionCanBeBumped(CWallet* wallet, const uint256& txid)
{
- vErrors.clear();
- bumpedTxid.SetNull();
- AssertLockHeld(pWallet->cs_wallet);
- auto it = pWallet->mapWallet.find(txid);
- if (it == pWallet->mapWallet.end()) {
- vErrors.push_back("Invalid or non-wallet transaction id");
- currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
- return;
+ LOCK2(cs_main, wallet->cs_wallet);
+ const CWalletTx* wtx = wallet->GetWalletTx(txid);
+ return wtx && SignalsOptInRBF(*wtx->tx) && !wtx->mapValue.count("replaced_by_txid");
+}
+
+Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors,
+ CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx)
+{
+ LOCK2(cs_main, wallet->cs_wallet);
+ errors.clear();
+ auto it = wallet->mapWallet.find(txid);
+ if (it == wallet->mapWallet.end()) {
+ errors.push_back("Invalid or non-wallet transaction id");
+ return Result::INVALID_ADDRESS_OR_KEY;
}
const CWalletTx& wtx = it->second;
- if (!preconditionChecks(pWallet, wtx)) {
- return;
+ Result result = PreconditionChecks(wallet, wtx, errors);
+ if (result != Result::OK) {
+ return result;
}
- if (!SignalsOptInRBF(wtx)) {
- vErrors.push_back("Transaction is not BIP 125 replaceable");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ if (!SignalsOptInRBF(*wtx.tx)) {
+ errors.push_back("Transaction is not BIP 125 replaceable");
+ return Result::WALLET_ERROR;
}
if (wtx.mapValue.count("replaced_by_txid")) {
- vErrors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid")));
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ errors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid")));
+ return Result::WALLET_ERROR;
}
// check that original tx consists entirely of our inputs
// if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
- if (!pWallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
- vErrors.push_back("Transaction contains inputs that don't belong to this wallet");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ if (!wallet->IsAllFromMe(*wtx.tx, ISMINE_SPENDABLE)) {
+ errors.push_back("Transaction contains inputs that don't belong to this wallet");
+ return Result::WALLET_ERROR;
}
// figure out which output was change
// if there was no change output or multiple change outputs, fail
int nOutput = -1;
for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
- if (pWallet->IsChange(wtx.tx->vout[i])) {
+ if (wallet->IsChange(wtx.tx->vout[i])) {
if (nOutput != -1) {
- vErrors.push_back("Transaction has multiple change outputs");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ errors.push_back("Transaction has multiple change outputs");
+ return Result::WALLET_ERROR;
}
nOutput = i;
}
}
if (nOutput == -1) {
- vErrors.push_back("Transaction does not have a change output");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ errors.push_back("Transaction does not have a change output");
+ return Result::WALLET_ERROR;
}
// Calculate the expected size of the new transaction.
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
- const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, pWallet);
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, wallet);
if (maxNewTxSize < 0) {
- vErrors.push_back("Transaction contains inputs that cannot be signed");
- currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
- return;
+ errors.push_back("Transaction contains inputs that cannot be signed");
+ return Result::INVALID_ADDRESS_OR_KEY;
}
// calculate the old fee and fee-rate
- nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
- CFeeRate nOldFeeRate(nOldFee, txSize);
+ old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(old_fee, txSize);
CFeeRate nNewFeeRate;
// The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
// future proof against changes to network wide policy for incremental relay
@@ -149,26 +148,24 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoin
walletIncrementalRelayFee = ::incrementalRelayFee;
}
- if (totalFee > 0) {
+ if (total_fee > 0) {
CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
- if (totalFee < minTotalFee) {
- vErrors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
+ if (total_fee < minTotalFee) {
+ errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
- currentResult = BumpFeeResult::INVALID_PARAMETER;
- return;
+ return Result::INVALID_PARAMETER;
}
CAmount requiredFee = GetRequiredFee(maxNewTxSize);
- if (totalFee < requiredFee) {
- vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
+ if (total_fee < requiredFee) {
+ errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
FormatMoney(requiredFee)));
- currentResult = BumpFeeResult::INVALID_PARAMETER;
- return;
+ return Result::INVALID_PARAMETER;
}
- nNewFee = totalFee;
- nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
+ new_fee = total_fee;
+ nNewFeeRate = CFeeRate(total_fee, maxNewTxSize);
} else {
- nNewFee = GetMinimumFee(maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */);
- nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
+ new_fee = GetMinimumFee(maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */);
+ nNewFeeRate = CFeeRate(new_fee, maxNewTxSize);
// New fee rate must be at least old rate + minimum incremental relay rate
// walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
@@ -177,47 +174,50 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoin
// add 1 satoshi to the result, because it may have been rounded down.
if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
- nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
+ new_fee = nNewFeeRate.GetFee(maxNewTxSize);
}
}
// Check that in all cases the new fee doesn't violate maxTxFee
- if (nNewFee > maxTxFee) {
- vErrors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
- FormatMoney(nNewFee), FormatMoney(maxTxFee)));
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ if (new_fee > maxTxFee) {
+ errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
+ FormatMoney(new_fee), FormatMoney(maxTxFee)));
+ return Result::WALLET_ERROR;
}
// check that fee rate is higher than mempool's minimum fee
// (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
// This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
// in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
- // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
+ // moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment.
CFeeRate minMempoolFeeRate = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
- vErrors.push_back(strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK())));
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ errors.push_back(strprintf(
+ "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- "
+ "the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction",
+ FormatMoney(nNewFeeRate.GetFeePerK()),
+ FormatMoney(minMempoolFeeRate.GetFeePerK()),
+ FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)),
+ FormatMoney(minMempoolFeeRate.GetFeePerK())));
+ return Result::WALLET_ERROR;
}
// Now modify the output to increase the fee.
// If the output is not large enough to pay the fee, fail.
- CAmount nDelta = nNewFee - nOldFee;
+ CAmount nDelta = new_fee - old_fee;
assert(nDelta > 0);
mtx = *wtx.tx;
CTxOut* poutput = &(mtx.vout[nOutput]);
if (poutput->nValue < nDelta) {
- vErrors.push_back("Change output is too small to bump the fee");
- currentResult = BumpFeeResult::WALLET_ERROR;
- return;
+ errors.push_back("Change output is too small to bump the fee");
+ return Result::WALLET_ERROR;
}
// If the output would become dust, discard it (converting the dust to fee)
poutput->nValue -= nDelta;
if (poutput->nValue <= GetDustThreshold(*poutput, ::dustRelayFee)) {
LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
- nNewFee += poutput->nValue;
+ new_fee += poutput->nValue;
mtx.vout.erase(mtx.vout.begin() + nOutput);
}
@@ -228,36 +228,36 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoin
}
}
- currentResult = BumpFeeResult::OK;
+ return Result::OK;
}
-bool CFeeBumper::signTransaction(CWallet *pWallet)
-{
- return pWallet->SignTransaction(mtx);
+bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx) {
+ LOCK2(cs_main, wallet->cs_wallet);
+ return wallet->SignTransaction(mtx);
}
-bool CFeeBumper::commit(CWallet *pWallet)
+Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid)
{
- AssertLockHeld(pWallet->cs_wallet);
- if (!vErrors.empty() || currentResult != BumpFeeResult::OK) {
- return false;
+ LOCK2(cs_main, wallet->cs_wallet);
+ if (!errors.empty()) {
+ return Result::MISC_ERROR;
}
- auto it = txid.IsNull() ? pWallet->mapWallet.end() : pWallet->mapWallet.find(txid);
- if (it == pWallet->mapWallet.end()) {
- vErrors.push_back("Invalid or non-wallet transaction id");
- currentResult = BumpFeeResult::MISC_ERROR;
- return false;
+ auto it = txid.IsNull() ? wallet->mapWallet.end() : wallet->mapWallet.find(txid);
+ if (it == wallet->mapWallet.end()) {
+ errors.push_back("Invalid or non-wallet transaction id");
+ return Result::MISC_ERROR;
}
CWalletTx& oldWtx = it->second;
// make sure the transaction still has no descendants and hasn't been mined in the meantime
- if (!preconditionChecks(pWallet, oldWtx)) {
- return false;
+ Result result = PreconditionChecks(wallet, oldWtx, errors);
+ if (result != Result::OK) {
+ return result;
}
- CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
+ CWalletTx wtxBumped(wallet, MakeTransactionRef(std::move(mtx)));
// commit/broadcast the tx
- CReserveKey reservekey(pWallet);
+ CReserveKey reservekey(wallet);
wtxBumped.mapValue = oldWtx.mapValue;
wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
wtxBumped.vOrderForm = oldWtx.vOrderForm;
@@ -265,27 +265,29 @@ bool CFeeBumper::commit(CWallet *pWallet)
wtxBumped.fTimeReceivedIsTxTime = true;
wtxBumped.fFromMe = true;
CValidationState state;
- if (!pWallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
+ if (!wallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
// NOTE: CommitTransaction never returns false, so this should never happen.
- vErrors.push_back(strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
- return false;
+ errors.push_back(strprintf("The transaction was rejected: %s", state.GetRejectReason()));
+ return Result::WALLET_ERROR;
}
- bumpedTxid = wtxBumped.GetHash();
+ bumped_txid = wtxBumped.GetHash();
if (state.IsInvalid()) {
// This can happen if the mempool rejected the transaction. Report
// what happened in the "errors" response.
- vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
+ errors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
}
// mark the original tx as bumped
- if (!pWallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) {
+ if (!wallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) {
// TODO: see if JSON-RPC has a standard way of returning a response
// along with an exception. It would be good to return information about
// wtxBumped to the caller even if marking the original transaction
// replaced does not succeed for some reason.
- vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
+ errors.push_back("Created new bumpfee transaction but could not mark the original transaction as replaced");
}
- return true;
+ return Result::OK;
}
+} // namespace feebumper
+
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
index 3d64e53c15..8eec30440c 100644
--- a/src/wallet/feebumper.h
+++ b/src/wallet/feebumper.h
@@ -13,7 +13,9 @@ class uint256;
class CCoinControl;
enum class FeeEstimateMode;
-enum class BumpFeeResult
+namespace feebumper {
+
+enum class Result
{
OK,
INVALID_ADDRESS_OR_KEY,
@@ -23,39 +25,34 @@ enum class BumpFeeResult
MISC_ERROR,
};
-class CFeeBumper
-{
-public:
- CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee);
- BumpFeeResult getResult() const { return currentResult; }
- const std::vector<std::string>& getErrors() const { return vErrors; }
- CAmount getOldFee() const { return nOldFee; }
- CAmount getNewFee() const { return nNewFee; }
- uint256 getBumpedTxId() const { return bumpedTxid; }
-
- /* signs the new transaction,
- * returns false if the tx couldn't be found or if it was
- * impossible to create the signature(s)
- */
- bool signTransaction(CWallet *pWallet);
-
- /* commits the fee bump,
- * returns true, in case of CWallet::CommitTransaction was successful
- * but, eventually sets vErrors if the tx could not be added to the mempool (will try later)
- * or if the old transaction could not be marked as replaced
- */
- bool commit(CWallet *pWalletNonConst);
-
-private:
- bool preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx);
-
- const uint256 txid;
- uint256 bumpedTxid;
- CMutableTransaction mtx;
- std::vector<std::string> vErrors;
- BumpFeeResult currentResult;
- CAmount nOldFee;
- CAmount nNewFee;
-};
+//! Return whether transaction can be bumped.
+bool TransactionCanBeBumped(CWallet* wallet, const uint256& txid);
+
+//! Create bumpfee transaction.
+Result CreateTransaction(const CWallet* wallet,
+ const uint256& txid,
+ const CCoinControl& coin_control,
+ CAmount total_fee,
+ std::vector<std::string>& errors,
+ CAmount& old_fee,
+ CAmount& new_fee,
+ CMutableTransaction& mtx);
+
+//! Sign the new transaction,
+//! @return false if the tx couldn't be found or if it was
+//! impossible to create the signature(s)
+bool SignTransaction(CWallet* wallet, CMutableTransaction& mtx);
+
+//! Commit the bumpfee transaction.
+//! @return success in case of CWallet::CommitTransaction was successful,
+//! but sets errors if the tx could not be added to the mempool (will try later)
+//! or if the old transaction could not be marked as replaced.
+Result CommitTransaction(CWallet* wallet,
+ const uint256& txid,
+ CMutableTransaction&& mtx,
+ std::vector<std::string>& errors,
+ uint256& bumped_txid);
+
+} // namespace feebumper
#endif // BITCOIN_WALLET_FEEBUMPER_H
diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp
index 76eeeeda05..73985dcf25 100644
--- a/src/wallet/fees.cpp
+++ b/src/wallet/fees.cpp
@@ -3,14 +3,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/fees.h"
+#include <wallet/fees.h>
-#include "policy/policy.h"
-#include "txmempool.h"
-#include "util.h"
-#include "validation.h"
-#include "wallet/coincontrol.h"
-#include "wallet/wallet.h"
+#include <policy/policy.h>
+#include <txmempool.h>
+#include <util.h>
+#include <validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/wallet.h>
CAmount GetRequiredFee(unsigned int nTxBytes)
diff --git a/src/wallet/fees.h b/src/wallet/fees.h
index 7b8a7dc868..225aff08ad 100644
--- a/src/wallet/fees.h
+++ b/src/wallet/fees.h
@@ -6,7 +6,7 @@
#ifndef BITCOIN_WALLET_FEES_H
#define BITCOIN_WALLET_FEES_H
-#include "amount.h"
+#include <amount.h>
class CBlockPolicyEstimator;
class CCoinControl;
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index c984df1df8..74036f4f0f 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -3,25 +3,28 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/init.h"
+#include <wallet/init.h>
-#include "net.h"
-#include "util.h"
-#include "utilmoneystr.h"
-#include "validation.h"
-#include "wallet/wallet.h"
-#include "wallet/rpcwallet.h"
+#include <net.h>
+#include <util.h>
+#include <utilmoneystr.h>
+#include <validation.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+#include <wallet/walletutil.h>
std::string GetWalletHelpString(bool showDebug)
{
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
+ strUsage += HelpMessageOpt("-addresstype", strprintf("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")", FormatOutputType(OUTPUT_TYPE_DEFAULT)));
+ strUsage += HelpMessageOpt("-changetype", "What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\"). Default is same as -addresstype, except when -addresstype=p2sh-segwit a native segwit output is used when sending to a native segwit address)");
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("-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("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
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)"),
@@ -30,11 +33,12 @@ std::string GetWalletHelpString(bool showDebug)
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("-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("-walletdir=<dir>", _("Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)"));
strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)"));
+ strUsage += HelpMessageOpt("-walletrbf", strprintf(_("Send transactions with full-RBF opt-in enabled (RPC only, default: %u)"), DEFAULT_WALLET_RBF));
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)"));
@@ -53,11 +57,16 @@ std::string GetWalletHelpString(bool showDebug)
bool WalletParameterInteraction()
{
- gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
- const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ for (const std::string& wallet : gArgs.GetArgs("-wallet")) {
+ LogPrintf("%s: parameter interaction: -disablewallet -> ignoring -wallet=%s\n", __func__, wallet);
+ }
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
return true;
+ }
+
+ gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
+ const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting -walletbroadcast=0\n", __func__);
@@ -168,20 +177,48 @@ bool WalletParameterInteraction()
bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
+ g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""));
+ if (g_address_type == OUTPUT_TYPE_NONE) {
+ return InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", "")));
+ }
+
+ // If changetype is set in config file or parameter, check that it's valid.
+ // Default to OUTPUT_TYPE_NONE if not set.
+ g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), OUTPUT_TYPE_NONE);
+ if (g_change_type == OUTPUT_TYPE_NONE && !gArgs.GetArg("-changetype", "").empty()) {
+ return InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", "")));
+ }
+
return true;
}
void RegisterWalletRPC(CRPCTable &t)
{
- if (gArgs.GetBoolArg("-disablewallet", false)) return;
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
+ return;
+ }
RegisterWalletRPCCommands(t);
}
bool VerifyWallets()
{
- if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
+ if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
return true;
+ }
+
+ if (gArgs.IsArgSet("-walletdir")) {
+ fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
+ if (!fs::exists(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string()));
+ } else if (!fs::is_directory(wallet_dir)) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string()));
+ } else if (!wallet_dir.is_absolute()) {
+ return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string()));
+ }
+ }
+
+ LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
uiInterface.InitMessage(_("Verifying wallet(s)..."));
@@ -197,7 +234,7 @@ bool VerifyWallets()
return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile));
}
- fs::path wallet_path = fs::absolute(walletFile, GetDataDir());
+ fs::path wallet_path = fs::absolute(walletFile, GetWalletDir());
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));
@@ -208,7 +245,7 @@ bool VerifyWallets()
}
std::string strError;
- if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) {
+ if (!CWalletDB::VerifyEnvironment(walletFile, GetWalletDir().string(), strError)) {
return InitError(strError);
}
@@ -222,7 +259,7 @@ bool VerifyWallets()
}
std::string strWarning;
- bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
+ bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetWalletDir().string(), strWarning, strError);
if (!strWarning.empty()) {
InitWarning(strWarning);
}
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 9539cc9f42..0b021f9fe0 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,23 +1,23 @@
-// 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 "base58.h"
-#include "chain.h"
-#include "rpc/safemode.h"
-#include "rpc/server.h"
-#include "init.h"
-#include "validation.h"
-#include "script/script.h"
-#include "script/standard.h"
-#include "sync.h"
-#include "util.h"
-#include "utiltime.h"
-#include "wallet.h"
-#include "merkleblock.h"
-#include "core_io.h"
-
-#include "rpcwallet.h"
+#include <base58.h>
+#include <chain.h>
+#include <rpc/safemode.h>
+#include <rpc/server.h>
+#include <wallet/init.h>
+#include <validation.h>
+#include <script/script.h>
+#include <script/standard.h>
+#include <sync.h>
+#include <util.h>
+#include <utiltime.h>
+#include <wallet/wallet.h>
+#include <merkleblock.h>
+#include <core_io.h>
+
+#include <wallet/rpcwallet.h>
#include <fstream>
#include <stdint.h>
@@ -80,13 +80,14 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
throw std::runtime_error(
- "importprivkey \"bitcoinprivkey\" ( \"label\" ) ( rescan )\n"
- "\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
+ "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n"
+ "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n"
"\nArguments:\n"
- "1. \"bitcoinprivkey\" (string, required) The private key (see dumpprivkey)\n"
+ "1. \"privkey\" (string, required) The private key (see dumpprivkey)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nDump a private key\n"
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
@@ -101,56 +102,65 @@ UniValue importprivkey(const JSONRPCRequest& request)
);
- LOCK2(cs_main, pwallet->cs_wallet);
-
- EnsureWalletIsUnlocked(pwallet);
+ WalletRescanReserver reserver(pwallet);
+ bool fRescan = true;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- std::string strSecret = request.params[0].get_str();
- std::string strLabel = "";
- if (!request.params[1].isNull())
- strLabel = request.params[1].get_str();
+ EnsureWalletIsUnlocked(pwallet);
- // Whether to perform rescan after import
- bool fRescan = true;
- if (!request.params[2].isNull())
- fRescan = request.params[2].get_bool();
+ std::string strSecret = request.params[0].get_str();
+ std::string strLabel = "";
+ if (!request.params[1].isNull())
+ strLabel = request.params[1].get_str();
- if (fRescan && fPruneMode)
- throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ // Whether to perform rescan after import
+ if (!request.params[2].isNull())
+ fRescan = request.params[2].get_bool();
- CBitcoinSecret vchSecret;
- bool fGood = vchSecret.SetString(strSecret);
+ if (fRescan && fPruneMode)
+ throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
- if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
- CKey key = vchSecret.GetKey();
- if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
+ CBitcoinSecret vchSecret;
+ bool fGood = vchSecret.SetString(strSecret);
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID vchAddress = pubkey.GetID();
- {
- pwallet->MarkDirty();
- pwallet->SetAddressBook(vchAddress, strLabel, "receive");
+ if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
- // Don't throw error in case a key is already there
- if (pwallet->HaveKey(vchAddress)) {
- return NullUniValue;
- }
+ CKey key = vchSecret.GetKey();
+ if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
- pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID vchAddress = pubkey.GetID();
+ {
+ pwallet->MarkDirty();
+ // We don't know which corresponding address will be used; label them all
+ for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
+ pwallet->SetAddressBook(dest, strLabel, "receive");
+ }
- if (!pwallet->AddKeyPubKey(key, pubkey)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
- }
+ // Don't throw error in case a key is already there
+ if (pwallet->HaveKey(vchAddress)) {
+ return NullUniValue;
+ }
- // whenever a key is imported, we need to scan the whole chain
- pwallet->UpdateTimeFirstKey(1);
+ // whenever a key is imported, we need to scan the whole chain
+ pwallet->UpdateTimeFirstKey(1);
+ pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;
- if (fRescan) {
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
+ }
+ pwallet->LearnAllRelatedScripts(pubkey);
}
}
+ if (fRescan) {
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
+ }
return NullUniValue;
}
@@ -165,7 +175,7 @@ UniValue abortrescan(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() > 0)
throw std::runtime_error(
"abortrescan\n"
- "\nStops current wallet rescan triggered e.g. by an importprivkey call.\n"
+ "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
"\nExamples:\n"
"\nImport a private key\n"
+ HelpExampleCli("importprivkey", "\"mykey\"") +
@@ -226,13 +236,14 @@ UniValue importaddress(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw std::runtime_error(
"importaddress \"address\" ( \"label\" rescan p2sh )\n"
- "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nAdds a script (in hex) or address that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
"\nArguments:\n"
"1. \"script\" (string, required) The hex-encoded script (or address)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"4. p2sh (boolean, optional, default=false) Add the P2SH version of the script as well\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"If you have the full public key, you should call importpubkey instead of this.\n"
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
"as change, and not show up in many RPCs.\n"
@@ -258,29 +269,35 @@ UniValue importaddress(const JSONRPCRequest& request)
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
// Whether to import a p2sh version, too
bool fP2SH = false;
if (!request.params[3].isNull())
fP2SH = request.params[3].get_bool();
- LOCK2(cs_main, pwallet->cs_wallet);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- CTxDestination dest = DecodeDestination(request.params[0].get_str());
- if (IsValidDestination(dest)) {
- if (fP2SH) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+ if (IsValidDestination(dest)) {
+ if (fP2SH) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
+ }
+ ImportAddress(pwallet, dest, strLabel);
+ } else if (IsHex(request.params[0].get_str())) {
+ std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
+ ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
+ } else {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
- ImportAddress(pwallet, dest, strLabel);
- } else if (IsHex(request.params[0].get_str())) {
- std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
- ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel, fP2SH);
- } else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script");
}
-
if (fRescan)
{
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
}
@@ -340,7 +357,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet);
- if (pwallet->IsMine(wtx)) {
+ if (pwallet->IsMine(*wtx.tx)) {
pwallet->AddToWallet(wtx, false);
return NullUniValue;
}
@@ -396,12 +413,13 @@ UniValue importpubkey(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw std::runtime_error(
"importpubkey \"pubkey\" ( \"label\" rescan )\n"
- "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
+ "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
"\nArguments:\n"
"1. \"pubkey\" (string, required) The hex-encoded public key\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
- "\nNote: This call can take minutes to complete if rescan is true.\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
"\nExamples:\n"
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
@@ -424,6 +442,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
if (fRescan && fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode");
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
if (!IsHex(request.params[0].get_str()))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string");
std::vector<unsigned char> data(ParseHex(request.params[0].get_str()));
@@ -431,14 +454,18 @@ UniValue importpubkey(const JSONRPCRequest& request)
if (!pubKey.IsFullyValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
- LOCK2(cs_main, pwallet->cs_wallet);
-
- ImportAddress(pwallet, pubKey.GetID(), strLabel);
- ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
+ ImportAddress(pwallet, dest, strLabel);
+ }
+ ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
+ pwallet->LearnAllRelatedScripts(pubKey);
+ }
if (fRescan)
{
- pwallet->RescanFromTime(TIMESTAMP_MIN, true /* update */);
+ pwallet->RescanFromTime(TIMESTAMP_MIN, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
}
@@ -456,7 +483,7 @@ UniValue importwallet(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"importwallet \"filename\"\n"
- "\nImports keys from a wallet dump file (see dumpwallet).\n"
+ "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The wallet file\n"
"\nExamples:\n"
@@ -471,78 +498,102 @@ UniValue importwallet(const JSONRPCRequest& request)
if (fPruneMode)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode");
- LOCK2(cs_main, pwallet->cs_wallet);
-
- EnsureWalletIsUnlocked(pwallet);
-
- std::ifstream file;
- file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
- if (!file.is_open())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
-
- int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+ int64_t nTimeBegin = 0;
bool fGood = true;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
- file.seekg(0, file.beg);
-
- pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
- while (file.good()) {
- pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
- std::string line;
- std::getline(file, line);
- if (line.empty() || line[0] == '#')
- continue;
-
- std::vector<std::string> vstr;
- boost::split(vstr, line, boost::is_any_of(" "));
- if (vstr.size() < 2)
- continue;
- CBitcoinSecret vchSecret;
- if (!vchSecret.SetString(vstr[0]))
- continue;
- CKey key = vchSecret.GetKey();
- CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
- CKeyID keyid = pubkey.GetID();
- if (pwallet->HaveKey(keyid)) {
- LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
- continue;
+ EnsureWalletIsUnlocked(pwallet);
+
+ std::ifstream file;
+ file.open(request.params[0].get_str().c_str(), std::ios::in | std::ios::ate);
+ if (!file.is_open()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
}
- int64_t nTime = DecodeDumpTime(vstr[1]);
- std::string strLabel;
- bool fLabel = true;
- for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
- if (boost::algorithm::starts_with(vstr[nStr], "#"))
- break;
- if (vstr[nStr] == "change=1")
- fLabel = false;
- if (vstr[nStr] == "reserve=1")
- fLabel = false;
- if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
- strLabel = DecodeDumpString(vstr[nStr].substr(6));
- fLabel = true;
+ nTimeBegin = chainActive.Tip()->GetBlockTime();
+
+ int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
+ file.seekg(0, file.beg);
+
+ pwallet->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
+ while (file.good()) {
+ pwallet->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
+ std::string line;
+ std::getline(file, line);
+ if (line.empty() || line[0] == '#')
+ continue;
+
+ std::vector<std::string> vstr;
+ boost::split(vstr, line, boost::is_any_of(" "));
+ if (vstr.size() < 2)
+ continue;
+ CBitcoinSecret vchSecret;
+ if (vchSecret.SetString(vstr[0])) {
+ CKey key = vchSecret.GetKey();
+ CPubKey pubkey = key.GetPubKey();
+ assert(key.VerifyPubKey(pubkey));
+ CKeyID keyid = pubkey.GetID();
+ if (pwallet->HaveKey(keyid)) {
+ LogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
+ continue;
+ }
+ int64_t nTime = DecodeDumpTime(vstr[1]);
+ std::string strLabel;
+ bool fLabel = true;
+ for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
+ if (boost::algorithm::starts_with(vstr[nStr], "#"))
+ break;
+ if (vstr[nStr] == "change=1")
+ fLabel = false;
+ if (vstr[nStr] == "reserve=1")
+ fLabel = false;
+ if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
+ strLabel = DecodeDumpString(vstr[nStr].substr(6));
+ fLabel = true;
+ }
+ }
+ LogPrintf("Importing %s...\n", EncodeDestination(keyid));
+ if (!pwallet->AddKeyPubKey(key, pubkey)) {
+ fGood = false;
+ continue;
+ }
+ pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
+ if (fLabel)
+ pwallet->SetAddressBook(keyid, strLabel, "receive");
+ nTimeBegin = std::min(nTimeBegin, nTime);
+ } else if(IsHex(vstr[0])) {
+ std::vector<unsigned char> vData(ParseHex(vstr[0]));
+ CScript script = CScript(vData.begin(), vData.end());
+ if (pwallet->HaveCScript(script)) {
+ LogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
+ continue;
+ }
+ if(!pwallet->AddCScript(script)) {
+ LogPrintf("Error importing script %s\n", vstr[0]);
+ fGood = false;
+ continue;
+ }
+ int64_t birth_time = DecodeDumpTime(vstr[1]);
+ if (birth_time > 0) {
+ pwallet->m_script_metadata[CScriptID(script)].nCreateTime = birth_time;
+ nTimeBegin = std::min(nTimeBegin, birth_time);
+ }
}
}
- LogPrintf("Importing %s...\n", EncodeDestination(keyid));
- if (!pwallet->AddKeyPubKey(key, pubkey)) {
- fGood = false;
- continue;
- }
- pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
- if (fLabel)
- pwallet->SetAddressBook(keyid, strLabel, "receive");
- nTimeBegin = std::min(nTimeBegin, nTime);
+ file.close();
+ pwallet->ShowProgress("", 100); // hide progress dialog in GUI
+ pwallet->UpdateTimeFirstKey(nTimeBegin);
}
- file.close();
- pwallet->ShowProgress("", 100); // hide progress dialog in GUI
- pwallet->UpdateTimeFirstKey(nTimeBegin);
- pwallet->RescanFromTime(nTimeBegin, false /* update */);
+ pwallet->RescanFromTime(nTimeBegin, reserver, false /* update */);
pwallet->MarkDirty();
if (!fGood)
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet");
return NullUniValue;
}
@@ -578,12 +629,12 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
- const CKeyID *keyID = boost::get<CKeyID>(&dest);
- if (!keyID) {
+ auto keyid = GetKeyForDestination(*pwallet, dest);
+ if (keyid.IsNull()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
}
CKey vchSecret;
- if (!pwallet->GetKey(*keyID, vchSecret)) {
+ if (!pwallet->GetKey(keyid, vchSecret)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
}
return CBitcoinSecret(vchSecret).ToString();
@@ -600,7 +651,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"dumpwallet \"filename\"\n"
- "\nDumps all wallet keys in a human-readable format.\n"
+ "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n"
+ "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n"
+ "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n"
+ "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The filename with path (either absolute or relative to bitcoind)\n"
"\nResult:\n"
@@ -616,9 +670,19 @@ UniValue dumpwallet(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet);
- std::ofstream file;
boost::filesystem::path filepath = request.params[0].get_str();
filepath = boost::filesystem::absolute(filepath);
+
+ /* Prevent arbitrary files from being overwritten. There have been reports
+ * that users have overwritten wallet files this way:
+ * https://github.com/bitcoin/bitcoin/issues/9934
+ * It may also avoid other security issues.
+ */
+ if (boost::filesystem::exists(filepath)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are sure this is what you want, move it out of the way first");
+ }
+
+ std::ofstream file;
file.open(filepath.string().c_str());
if (!file.is_open())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
@@ -627,6 +691,9 @@ UniValue dumpwallet(const JSONRPCRequest& request)
const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys();
pwallet->GetKeyBirthTimes(mapKeyBirth);
+ std::set<CScriptID> scripts = pwallet->GetCScripts();
+ // TODO: include scripts in GetKeyBirthTimes() output instead of separate
+
// sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
for (const auto& entry : mapKeyBirth) {
@@ -644,7 +711,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
file << "\n";
- // add the base58check encoded extended master if the wallet uses HD
+ // add the base58check encoded extended master if the wallet uses HD
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull())
{
@@ -681,6 +748,21 @@ UniValue dumpwallet(const JSONRPCRequest& request)
}
}
file << "\n";
+ for (const CScriptID &scriptid : scripts) {
+ CScript script;
+ std::string create_time = "0";
+ std::string address = EncodeDestination(scriptid);
+ // get birth times for scripts with metadata
+ auto it = pwallet->m_script_metadata.find(scriptid);
+ if (it != pwallet->m_script_metadata.end()) {
+ create_time = EncodeDumpTime(it->second.nCreateTime);
+ }
+ if(pwallet->GetCScript(scriptid, script)) {
+ file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time);
+ file << strprintf(" # addr=%s\n", address);
+ }
+ }
+ file << "\n";
file << "# End of dump\n";
file.close();
@@ -708,8 +790,8 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6
const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
- const bool& internal = data.exists("internal") ? data["internal"].get_bool() : false;
- const bool& watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
+ const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
const std::string& label = data.exists("label") && !internal ? data["label"].get_str() : "";
bool isScript = scriptPubKey.getType() == UniValue::VSTR;
@@ -951,7 +1033,7 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6
pwallet->SetAddressBook(vchAddress, label, "receive");
if (pwallet->HaveKey(vchAddress)) {
- return false;
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
}
pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp;
@@ -1029,7 +1111,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2)
throw std::runtime_error(
"importmulti \"requests\" ( \"options\" )\n\n"
- "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options).\n\n"
+ "Import addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n\n"
"Arguments:\n"
"1. requests (array, required) Data to be imported\n"
" [ (array of json objects)\n"
@@ -1054,6 +1136,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
" {\n"
" \"rescan\": <false>, (boolean, optional, default: true) Stating if should rescan the blockchain after all imports\n"
" }\n"
+ "\nNote: This call can take minutes to complete if rescan is true, during that time, other rpc calls\n"
+ "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n"
"\nExamples:\n" +
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
"{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
@@ -1079,49 +1163,55 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
}
}
- LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked(pwallet);
-
- // Verify all timestamps are present before importing any keys.
- const int64_t now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
- for (const UniValue& data : requests.getValues()) {
- GetImportTimestamp(data, now);
+ WalletRescanReserver reserver(pwallet);
+ if (fRescan && !reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
+ int64_t now = 0;
bool fRunScan = false;
- const int64_t minimumTimestamp = 1;
int64_t nLowestTimestamp = 0;
-
- if (fRescan && chainActive.Tip()) {
- nLowestTimestamp = chainActive.Tip()->GetBlockTime();
- } else {
- fRescan = false;
- }
-
UniValue response(UniValue::VARR);
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
- for (const UniValue& data : requests.getValues()) {
- const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
- const UniValue result = ProcessImport(pwallet, data, timestamp);
- response.push_back(result);
-
- if (!fRescan) {
- continue;
+ // Verify all timestamps are present before importing any keys.
+ now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0;
+ for (const UniValue& data : requests.getValues()) {
+ GetImportTimestamp(data, now);
}
- // If at least one request was successful then allow rescan.
- if (result["success"].get_bool()) {
- fRunScan = true;
+ const int64_t minimumTimestamp = 1;
+
+ if (fRescan && chainActive.Tip()) {
+ nLowestTimestamp = chainActive.Tip()->GetBlockTime();
+ } else {
+ fRescan = false;
}
- // Get the lowest timestamp.
- if (timestamp < nLowestTimestamp) {
- nLowestTimestamp = timestamp;
+ for (const UniValue& data : requests.getValues()) {
+ const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
+ const UniValue result = ProcessImport(pwallet, data, timestamp);
+ response.push_back(result);
+
+ if (!fRescan) {
+ continue;
+ }
+
+ // If at least one request was successful then allow rescan.
+ if (result["success"].get_bool()) {
+ fRunScan = true;
+ }
+
+ // Get the lowest timestamp.
+ if (timestamp < nLowestTimestamp) {
+ nLowestTimestamp = timestamp;
+ }
}
}
-
if (fRescan && fRunScan && requests.size()) {
- int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, true /* update */);
+ int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
pwallet->ReacceptWalletTransactions();
if (scannedTime > nLowestTimestamp) {
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index ae89b3c0a1..33790d3490 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,31 +1,33 @@
// Copyright (c) 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 "amount.h"
-#include "base58.h"
-#include "chain.h"
-#include "consensus/validation.h"
-#include "core_io.h"
-#include "httpserver.h"
-#include "validation.h"
-#include "net.h"
-#include "policy/feerate.h"
-#include "policy/fees.h"
-#include "policy/policy.h"
-#include "policy/rbf.h"
-#include "rpc/mining.h"
-#include "rpc/safemode.h"
-#include "rpc/server.h"
-#include "script/sign.h"
-#include "timedata.h"
-#include "util.h"
-#include "utilmoneystr.h"
-#include "wallet/coincontrol.h"
-#include "wallet/feebumper.h"
-#include "wallet/wallet.h"
-#include "wallet/walletdb.h"
+#include <amount.h>
+#include <base58.h>
+#include <chain.h>
+#include <consensus/validation.h>
+#include <core_io.h>
+#include <httpserver.h>
+#include <validation.h>
+#include <net.h>
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <policy/rbf.h>
+#include <rpc/mining.h>
+#include <rpc/safemode.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <script/sign.h>
+#include <timedata.h>
+#include <util.h>
+#include <utilmoneystr.h>
+#include <wallet/coincontrol.h>
+#include <wallet/feebumper.h>
+#include <wallet/wallet.h>
+#include <wallet/walletdb.h>
+#include <wallet/walletutil.h>
#include <init.h> // For StartShutdown
@@ -108,7 +110,7 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
std::string rbfStatus = "no";
if (confirms <= 0) {
LOCK(mempool.cs);
- RBFTransactionState rbfState = IsRBFOptIn(wtx, mempool);
+ RBFTransactionState rbfState = IsRBFOptIn(*wtx.tx, mempool);
if (rbfState == RBF_TRANSACTIONSTATE_UNKNOWN)
rbfStatus = "unknown";
else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125)
@@ -135,14 +137,15 @@ UniValue getnewaddress(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() > 1)
+ if (request.fHelp || request.params.size() > 2)
throw std::runtime_error(
- "getnewaddress ( \"account\" )\n"
+ "getnewaddress ( \"account\" \"address_type\" )\n"
"\nReturns a new Bitcoin address for receiving payments.\n"
"If 'account' is specified (DEPRECATED), it is added to the address book \n"
"so payments received with the address will be credited to 'account'.\n"
"\nArguments:\n"
"1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
+ "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
@@ -157,6 +160,14 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!request.params[0].isNull())
strAccount = AccountFromValue(request.params[0]);
+ OutputType output_type = g_address_type;
+ if (!request.params[1].isNull()) {
+ output_type = ParseOutputType(request.params[1].get_str(), g_address_type);
+ if (output_type == OUTPUT_TYPE_NONE) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
+ }
+ }
+
if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
@@ -166,22 +177,23 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
- CKeyID keyID = newKey.GetID();
+ pwallet->LearnRelatedScripts(newKey, output_type);
+ CTxDestination dest = GetDestinationForKey(newKey, output_type);
- pwallet->SetAddressBook(keyID, strAccount, "receive");
+ pwallet->SetAddressBook(dest, strAccount, "receive");
- return EncodeDestination(keyID);
+ return EncodeDestination(dest);
}
-CTxDestination GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false)
+CTxDestination GetAccountDestination(CWallet* const pwallet, std::string strAccount, bool bForceNew=false)
{
- CPubKey pubKey;
- if (!pwallet->GetAccountPubkey(pubKey, strAccount, bForceNew)) {
+ CTxDestination dest;
+ if (!pwallet->GetAccountDestination(dest, strAccount, bForceNew)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
- return pubKey.GetID();
+ return dest;
}
UniValue getaccountaddress(const JSONRPCRequest& request)
@@ -213,7 +225,7 @@ UniValue getaccountaddress(const JSONRPCRequest& request)
UniValue ret(UniValue::VSTR);
- ret = EncodeDestination(GetAccountAddress(pwallet, strAccount));
+ ret = EncodeDestination(GetAccountDestination(pwallet, strAccount));
return ret;
}
@@ -225,11 +237,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() > 0)
+ if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
- "getrawchangeaddress\n"
+ "getrawchangeaddress ( \"address_type\" )\n"
"\nReturns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n"
+ "\nArguments:\n"
+ "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
"\nResult:\n"
"\"address\" (string) The address\n"
"\nExamples:\n"
@@ -243,6 +257,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();
}
+ OutputType output_type = g_change_type != OUTPUT_TYPE_NONE ? g_change_type : g_address_type;
+ if (!request.params[0].isNull()) {
+ output_type = ParseOutputType(request.params[0].get_str(), output_type);
+ if (output_type == OUTPUT_TYPE_NONE) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ }
+ }
+
CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
if (!reservekey.GetReservedKey(vchPubKey, true))
@@ -250,9 +272,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
reservekey.KeepKey();
- CKeyID keyID = vchPubKey.GetID();
+ pwallet->LearnRelatedScripts(vchPubKey, output_type);
+ CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
- return EncodeDestination(keyID);
+ return EncodeDestination(dest);
}
@@ -291,8 +314,8 @@ UniValue setaccount(const JSONRPCRequest& request)
// Detect when changing the account of an address that is the 'unused current key' of another account:
if (pwallet->mapAddressBook.count(dest)) {
std::string strOldAccount = pwallet->mapAddressBook[dest].name;
- if (dest == GetAccountAddress(pwallet, strOldAccount)) {
- GetAccountAddress(pwallet, strOldAccount, true);
+ if (dest == GetAccountDestination(pwallet, strOldAccount)) {
+ GetAccountDestination(pwallet, strOldAccount, true);
}
}
pwallet->SetAddressBook(dest, strAccount, "receive");
@@ -455,6 +478,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
@@ -533,6 +561,11 @@ UniValue listaddressgroupings(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR);
@@ -645,6 +678,11 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
// Bitcoin address
@@ -654,7 +692,7 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request)
}
CScript scriptPubKey = GetScriptForDestination(dest);
if (!IsMine(*pwallet, scriptPubKey)) {
- return ValueFromAmount(0);
+ throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
}
// Minimum confirmations
@@ -707,6 +745,11 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
// Minimum confirmations
@@ -750,6 +793,8 @@ UniValue getbalance(const JSONRPCRequest& request)
throw std::runtime_error(
"getbalance ( \"account\" minconf include_watchonly )\n"
"\nIf account is not specified, returns the server's total available balance.\n"
+ "The available balance is what the wallet considers currently spendable, and is\n"
+ "thus affected by options which limit spendability such as -spendzeroconfchange.\n"
"If account is specified (DEPRECATED), returns the balance in the account.\n"
"Note that the account \"\" is not the same as leaving the parameter out.\n"
"The server total may be different to the balance in the default \"\" account.\n"
@@ -780,6 +825,11 @@ UniValue getbalance(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
const UniValue& account_value = request.params[0];
@@ -825,6 +875,11 @@ UniValue getunconfirmedbalance(const JSONRPCRequest &request)
"Returns the server's total unconfirmed balance\n");
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
return ValueFromAmount(pwallet->GetUnconfirmedBalance());
@@ -919,6 +974,11 @@ UniValue sendfrom(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
std::string strAccount = AccountFromValue(request.params[0]);
@@ -1000,10 +1060,15 @@ UniValue sendmany(const JSONRPCRequest& request)
"\nSend two amounts to two different addresses, subtract fee from amount:\n"
+ HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
"\nAs a json rpc call\n"
- + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"")
+ + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"")
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
if (pwallet->GetBroadcastTransactions() && !g_connman) {
@@ -1097,9 +1162,6 @@ UniValue sendmany(const JSONRPCRequest& request)
return wtx.GetHash().GetHex();
}
-// Defined in rpc/misc.cpp
-extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params);
-
UniValue addmultisigaddress(const JSONRPCRequest& request)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -1107,24 +1169,32 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
- {
- std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n"
- "\nAdd a nrequired-to-sign multisignature address to the wallet.\n"
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
+ std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" \"address_type\" )\n"
+ "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
"Each key is a Bitcoin address or hex-encoded public key.\n"
+ "This functionality is only intended for use with non-watchonly addresses.\n"
+ "See `importaddress` for watchonly p2sh address support.\n"
"If 'account' is specified (DEPRECATED), assign address to that account.\n"
"\nArguments:\n"
- "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
- "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
+ "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
+ "2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
" [\n"
- " \"address\" (string) bitcoin address or hex-encoded public key\n"
+ " \"address\" (string) bitcoin address or hex-encoded public key\n"
" ...,\n"
" ]\n"
- "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
+ "3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
+ "4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
- "\"address\" (string) A bitcoin address associated with the keys.\n"
+ "{\n"
+ " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
+ " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
+ "}\n"
+ "\nResult (DEPRECATED. To see this result in v0.16 instead, please start bitcoind with -deprecatedrpc=addmultisigaddress).\n"
+ " clients should transition to the new output api before upgrading to v0.17.\n"
+ "\"address\" (string) A bitcoin address associated with the keys.\n"
"\nExamples:\n"
"\nAdd a multisig address from 2 addresses\n"
@@ -1141,40 +1211,61 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
if (!request.params[2].isNull())
strAccount = AccountFromValue(request.params[2]);
+ int required = request.params[0].get_int();
+
+ // Get the public keys
+ const UniValue& keys_or_addrs = request.params[1].get_array();
+ std::vector<CPubKey> pubkeys;
+ for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
+ if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
+ pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
+ } else {
+ pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str()));
+ }
+ }
+
+ OutputType output_type = g_address_type;
+ if (!request.params[3].isNull()) {
+ output_type = ParseOutputType(request.params[3].get_str(), output_type);
+ if (output_type == OUTPUT_TYPE_NONE) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
+ }
+ }
+
// Construct using pay-to-script-hash:
- CScript inner = _createmultisig_redeemScript(pwallet, request.params);
- CScriptID innerID(inner);
+ CScript inner = CreateMultisigRedeemscript(required, pubkeys);
pwallet->AddCScript(inner);
+ CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type);
+ pwallet->SetAddressBook(dest, strAccount, "send");
+
+ // Return old style interface
+ if (IsDeprecatedRPCEnabled("addmultisigaddress")) {
+ return EncodeDestination(dest);
+ }
- pwallet->SetAddressBook(innerID, strAccount, "send");
- return EncodeDestination(innerID);
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", EncodeDestination(dest));
+ result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
+ return result;
}
class Witnessifier : public boost::static_visitor<bool>
{
public:
CWallet * const pwallet;
- CScriptID result;
-
- explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {}
+ CTxDestination result;
+ bool already_witness;
- bool operator()(const CNoDestination &dest) const { return false; }
+ explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet), already_witness(false) {}
bool operator()(const CKeyID &keyID) {
if (pwallet) {
CScript basescript = GetScriptForDestination(keyID);
CScript witscript = GetScriptForWitness(basescript);
- SignatureData sigs;
- // This check is to make sure that the script we created can actually be solved for and signed by us
- // if we were to have the private keys. This is just to make sure that the script is valid and that,
- // if found in a transaction, we would still accept and relay that transaction.
- if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
- !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
+ if (!IsSolvable(*pwallet, witscript)) {
return false;
}
- pwallet->AddCScript(witscript);
- result = CScriptID(witscript);
- return true;
+ return ExtractDestination(witscript, result);
}
return false;
}
@@ -1185,24 +1276,35 @@ public:
int witnessversion;
std::vector<unsigned char> witprog;
if (subscript.IsWitnessProgram(witnessversion, witprog)) {
- result = scriptID;
+ ExtractDestination(subscript, result);
+ already_witness = true;
return true;
}
CScript witscript = GetScriptForWitness(subscript);
- SignatureData sigs;
- // This check is to make sure that the script we created can actually be solved for and signed by us
- // if we were to have the private keys. This is just to make sure that the script is valid and that,
- // if found in a transaction, we would still accept and relay that transaction.
- if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
- !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
+ if (!IsSolvable(*pwallet, witscript)) {
return false;
}
- pwallet->AddCScript(witscript);
- result = CScriptID(witscript);
- return true;
+ return ExtractDestination(witscript, result);
}
return false;
}
+
+ bool operator()(const WitnessV0KeyHash& id)
+ {
+ already_witness = true;
+ result = id;
+ return true;
+ }
+
+ bool operator()(const WitnessV0ScriptHash& id)
+ {
+ already_witness = true;
+ result = id;
+ return true;
+ }
+
+ template<typename T>
+ bool operator()(const T& dest) { return false; }
};
UniValue addwitnessaddress(const JSONRPCRequest& request)
@@ -1212,22 +1314,30 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 1)
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
{
- std::string msg = "addwitnessaddress \"address\"\n"
- "\nAdd a witness address for a script (with pubkey or redeemscript known).\n"
+ std::string msg = "addwitnessaddress \"address\" ( p2sh )\n"
+ "\nDEPRECATED: set the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n"
+ "Add a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\n"
"It returns the witness script.\n"
"\nArguments:\n"
"1. \"address\" (string, required) An address known to the wallet\n"
+ "2. p2sh (bool, optional, default=true) Embed inside P2SH\n"
"\nResult:\n"
- "\"witnessaddress\", (string) The value of the new address (P2SH of witness script).\n"
+ "\"witnessaddress\", (string) The value of the new address (P2SH or BIP173).\n"
"}\n"
;
throw std::runtime_error(msg);
}
+ if (!IsDeprecatedRPCEnabled("addwitnessaddress")) {
+ throw JSONRPCError(RPC_METHOD_DEPRECATED, "addwitnessaddress is deprecated and will be fully removed in v0.17. "
+ "To use addwitnessaddress in v0.16, restart bitcoind with -deprecatedrpc=addwitnessaddress.\n"
+ "Projects should transition to using the address_type argument of getnewaddress, or option -addresstype=[bech32|p2sh-segwit] instead.\n");
+ }
+
{
LOCK(cs_main);
if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) {
@@ -1240,13 +1350,31 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
+ bool p2sh = true;
+ if (!request.params[1].isNull()) {
+ p2sh = request.params[1].get_bool();
+ }
+
Witnessifier w(pwallet);
bool ret = boost::apply_visitor(w, dest);
if (!ret) {
throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed");
}
- pwallet->SetAddressBook(w.result, "", "receive");
+ CScript witprogram = GetScriptForDestination(w.result);
+
+ if (p2sh) {
+ w.result = CScriptID(witprogram);
+ }
+
+ if (w.already_witness) {
+ if (!(dest == w.result)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types");
+ }
+ } else {
+ pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software
+ pwallet->SetAddressBook(w.result, "", "receive");
+ }
return EncodeDestination(w.result);
}
@@ -1366,14 +1494,14 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
if (fByAccounts)
{
- for (std::map<std::string, tallyitem>::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it)
+ for (const auto& entry : mapAccountTally)
{
- CAmount nAmount = (*it).second.nAmount;
- int nConf = (*it).second.nConf;
+ CAmount nAmount = entry.second.nAmount;
+ int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
- if((*it).second.fIsWatchonly)
+ if (entry.second.fIsWatchonly)
obj.push_back(Pair("involvesWatchonly", true));
- obj.push_back(Pair("account", (*it).first));
+ obj.push_back(Pair("account", entry.first));
obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
ret.push_back(obj);
@@ -1423,6 +1551,11 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
return ListReceived(pwallet, request.params, false);
@@ -1463,6 +1596,11 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
return ListReceived(pwallet, request.params, true);
@@ -1651,7 +1789,10 @@ UniValue listtransactions(const JSONRPCRequest& request)
);
ObserveSafeMode();
- LOCK2(cs_main, pwallet->cs_wallet);
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
std::string strAccount = "*";
if (!request.params[0].isNull())
@@ -1674,20 +1815,25 @@ UniValue listtransactions(const JSONRPCRequest& request)
UniValue ret(UniValue::VARR);
- const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
-
- // iterate backwards until we have nCount items to return:
- for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
- CWalletTx *const pwtx = (*it).second.first;
- if (pwtx != nullptr)
- ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter);
- CAccountingEntry *const pacentry = (*it).second.second;
- if (pacentry != nullptr)
- AcentryToJSON(*pacentry, strAccount, ret);
+ LOCK2(cs_main, pwallet->cs_wallet);
+
+ const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
- if ((int)ret.size() >= (nCount+nFrom)) break;
+ // iterate backwards until we have nCount items to return:
+ for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
+ {
+ CWalletTx *const pwtx = (*it).second.first;
+ if (pwtx != nullptr)
+ ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter);
+ CAccountingEntry *const pacentry = (*it).second.second;
+ if (pacentry != nullptr)
+ AcentryToJSON(*pacentry, strAccount, ret);
+
+ if ((int)ret.size() >= (nCount+nFrom)) break;
+ }
}
+
// ret is newest to oldest
if (nFrom > (int)ret.size())
@@ -1745,6 +1891,11 @@ UniValue listaccounts(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
int nMinDepth = 1;
@@ -1854,6 +2005,11 @@ UniValue listsinceblock(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain.
@@ -1861,19 +2017,20 @@ UniValue listsinceblock(const JSONRPCRequest& request)
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
- if (!request.params[0].isNull()) {
+ if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
uint256 blockId;
blockId.SetHex(request.params[0].get_str());
BlockMap::iterator it = mapBlockIndex.find(blockId);
- if (it != mapBlockIndex.end()) {
- paltindex = pindex = it->second;
- if (chainActive[pindex->nHeight] != pindex) {
- // the block being asked for is a part of a deactivated chain;
- // we don't want to depend on its perceived height in the block
- // chain, we want to instead use the last common ancestor
- pindex = chainActive.FindFork(pindex);
- }
+ if (it == mapBlockIndex.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
+ }
+ paltindex = pindex = it->second;
+ if (chainActive[pindex->nHeight] != pindex) {
+ // the block being asked for is a part of a deactivated chain;
+ // we don't want to depend on its perceived height in the block
+ // chain, we want to instead use the last common ancestor
+ pindex = chainActive.FindFork(pindex);
}
}
@@ -1986,6 +2143,11 @@ UniValue gettransaction(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
uint256 hash;
@@ -2018,7 +2180,7 @@ UniValue gettransaction(const JSONRPCRequest& request)
ListTransactions(pwallet, wtx, "*", 0, false, details, filter);
entry.push_back(Pair("details", details));
- std::string strHex = EncodeHexTx(static_cast<CTransaction>(wtx), RPCSerializationFlags());
+ std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags());
entry.push_back(Pair("hex", strHex));
return entry;
@@ -2048,6 +2210,11 @@ UniValue abandontransaction(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
uint256 hash;
@@ -2082,6 +2249,10 @@ UniValue backupwallet(const JSONRPCRequest& request)
+ HelpExampleRpc("backupwallet", "\"backup.dat\"")
);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
std::string strDest = request.params[0].get_str();
@@ -2147,14 +2318,15 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
return NullUniValue;
}
- if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) {
+ if (request.fHelp || request.params.size() != 2) {
throw std::runtime_error(
"walletpassphrase \"passphrase\" timeout\n"
"\nStores the wallet decryption key in memory for 'timeout' seconds.\n"
"This is needed prior to performing transactions related to private keys such as sending bitcoins\n"
"\nArguments:\n"
"1. \"passphrase\" (string, required) The wallet passphrase\n"
- "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n"
+ "2. timeout (numeric, required) The time to keep the decryption key in seconds. Limited to at most 1073741824 (2^30) seconds.\n"
+ " Any value greater than 1073741824 seconds will be set to 1073741824 seconds.\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n"
"time that overrides the old one.\n"
@@ -2183,6 +2355,17 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
strWalletPass = request.params[0].get_str().c_str();
+ // Get the timeout
+ int64_t nSleepTime = request.params[1].get_int64();
+ // Timeout cannot be negative, otherwise it will relock immediately
+ if (nSleepTime < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative.");
+ }
+ // Clamp timeout to 2^30 seconds
+ if (nSleepTime > (int64_t)1 << 30) {
+ nSleepTime = (int64_t)1 << 30;
+ }
+
if (strWalletPass.length() > 0)
{
if (!pwallet->Unlock(strWalletPass)) {
@@ -2196,7 +2379,6 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
pwallet->TopUpKeyPool();
- int64_t nSleepTime = request.params[1].get_int64();
pwallet->nRelockTime = GetTime() + nSleepTime;
RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime);
@@ -2211,7 +2393,7 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request)
return NullUniValue;
}
- if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) {
+ if (request.fHelp || request.params.size() != 2) {
throw std::runtime_error(
"walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n"
"\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n"
@@ -2262,7 +2444,7 @@ UniValue walletlock(const JSONRPCRequest& request)
return NullUniValue;
}
- if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 0)) {
+ if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"walletlock\n"
"\nRemoves the wallet encryption key from memory, locking the wallet.\n"
@@ -2302,7 +2484,7 @@ UniValue encryptwallet(const JSONRPCRequest& request)
return NullUniValue;
}
- if (!pwallet->IsCrypted() && (request.fHelp || request.params.size() != 1)) {
+ if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"encryptwallet \"passphrase\"\n"
"\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n"
@@ -2401,6 +2583,10 @@ UniValue lockunspent(const JSONRPCRequest& request)
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
);
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
@@ -2415,12 +2601,15 @@ UniValue lockunspent(const JSONRPCRequest& request)
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
- UniValue outputs = request.params[1].get_array();
- for (unsigned int idx = 0; idx < outputs.size(); idx++) {
- const UniValue& output = outputs[idx];
- if (!output.isObject())
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object");
- const UniValue& o = output.get_obj();
+ const UniValue& output_params = request.params[1];
+
+ // Create and validate the COutPoints first.
+
+ std::vector<COutPoint> outputs;
+ outputs.reserve(output_params.size());
+
+ for (unsigned int idx = 0; idx < output_params.size(); idx++) {
+ const UniValue& o = output_params[idx].get_obj();
RPCTypeCheckObj(o,
{
@@ -2428,20 +2617,50 @@ UniValue lockunspent(const JSONRPCRequest& request)
{"vout", UniValueType(UniValue::VNUM)},
});
- std::string txid = find_value(o, "txid").get_str();
- if (!IsHex(txid))
+ const std::string& txid = find_value(o, "txid").get_str();
+ if (!IsHex(txid)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid");
+ }
- int nOutput = find_value(o, "vout").get_int();
- if (nOutput < 0)
+ const int nOutput = find_value(o, "vout").get_int();
+ if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");
+ }
- COutPoint outpt(uint256S(txid), nOutput);
+ const COutPoint outpt(uint256S(txid), nOutput);
- if (fUnlock)
- pwallet->UnlockCoin(outpt);
- else
- pwallet->LockCoin(outpt);
+ const auto it = pwallet->mapWallet.find(outpt.hash);
+ if (it == pwallet->mapWallet.end()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
+ }
+
+ const CWalletTx& trans = it->second;
+
+ if (outpt.n >= trans.tx->vout.size()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
+ }
+
+ if (pwallet->IsSpent(outpt.hash, outpt.n)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
+ }
+
+ const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n);
+
+ if (fUnlock && !is_locked) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
+ }
+
+ if (!fUnlock && is_locked) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
+ }
+
+ outputs.push_back(outpt);
+ }
+
+ // Atomically set (un)locked status for the outputs.
+ for (const COutPoint& outpt : outputs) {
+ if (fUnlock) pwallet->UnlockCoin(outpt);
+ else pwallet->LockCoin(outpt);
}
return true;
@@ -2552,7 +2771,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
" \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
- " \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
+ " \"hdmasterkeyid\": \"<hash160>\" (string, optional) the Hash160 of the HD master pubkey (only present when HD is enabled)\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "")
@@ -2560,6 +2779,11 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
);
ObserveSafeMode();
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
UniValue obj(UniValue::VOBJ);
@@ -2769,9 +2993,12 @@ UniValue listunspent(const JSONRPCRequest& request)
nMaximumCount = options["maximumCount"].get_int64();
}
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
UniValue results(UniValue::VARR);
std::vector<COutput> vecOutputs;
- assert(pwallet != nullptr);
LOCK2(cs_main, pwallet->cs_wallet);
pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth);
@@ -2822,9 +3049,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
throw std::runtime_error(
- "fundrawtransaction \"hexstring\" ( options )\n"
+ "fundrawtransaction \"hexstring\" ( options iswitness )\n"
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
"This will not modify existing inputs, and will add at most one change output to the outputs.\n"
"No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n"
@@ -2841,6 +3068,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
" {\n"
" \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
" \"changePosition\" (numeric, optional, default random) The index of the change output\n"
+ " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
" \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
" \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
" \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n"
@@ -2859,6 +3087,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
" \"CONSERVATIVE\"\n"
" }\n"
" for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n"
+ "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n"
+ " If iswitness is not present, heuristic tests will be used in decoding\n"
+
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
@@ -2879,6 +3110,10 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
ObserveSafeMode();
RPCTypeCheck(request.params, {UniValue::VSTR});
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
CCoinControl coinControl;
int changePosition = -1;
bool lockUnspents = false;
@@ -2891,7 +3126,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
coinControl.fAllowWatchOnly = request.params[1].get_bool();
}
else {
- RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL});
UniValue options = request.params[1];
@@ -2899,6 +3134,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
{
{"changeAddress", UniValueType(UniValue::VSTR)},
{"changePosition", UniValueType(UniValue::VNUM)},
+ {"change_type", UniValueType(UniValue::VSTR)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"lockUnspents", UniValueType(UniValue::VBOOL)},
{"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so.
@@ -2923,6 +3159,16 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (options.exists("changePosition"))
changePosition = options["changePosition"].get_int();
+ if (options.exists("change_type")) {
+ if (options.exists("changeAddress")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both changeAddress and address_type options");
+ }
+ coinControl.change_type = ParseOutputType(options["change_type"].get_str(), coinControl.change_type);
+ if (coinControl.change_type == OUTPUT_TYPE_NONE) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown change type '%s'", options["change_type"].get_str()));
+ }
+ }
+
if (options.exists("includeWatching"))
coinControl.fAllowWatchOnly = options["includeWatching"].get_bool();
@@ -2960,8 +3206,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
// parse hex string from parameter
CMutableTransaction tx;
- if (!DecodeHexTx(tx, request.params[0].get_str(), true))
+ bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool();
+ bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool();
+ if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ }
if (tx.vout.size() == 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
@@ -3089,48 +3338,57 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
}
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ pwallet->BlockUntilSyncedToCurrentChain();
+
LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
- CFeeBumper feeBump(pwallet, hash, coin_control, totalFee);
- BumpFeeResult res = feeBump.getResult();
- if (res != BumpFeeResult::OK)
- {
+
+ std::vector<std::string> errors;
+ CAmount old_fee;
+ CAmount new_fee;
+ CMutableTransaction mtx;
+ feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx);
+ if (res != feebumper::Result::OK) {
switch(res) {
- case BumpFeeResult::INVALID_ADDRESS_OR_KEY:
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, feeBump.getErrors()[0]);
+ case feebumper::Result::INVALID_ADDRESS_OR_KEY:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errors[0]);
break;
- case BumpFeeResult::INVALID_REQUEST:
- throw JSONRPCError(RPC_INVALID_REQUEST, feeBump.getErrors()[0]);
+ case feebumper::Result::INVALID_REQUEST:
+ throw JSONRPCError(RPC_INVALID_REQUEST, errors[0]);
break;
- case BumpFeeResult::INVALID_PARAMETER:
- throw JSONRPCError(RPC_INVALID_PARAMETER, feeBump.getErrors()[0]);
+ case feebumper::Result::INVALID_PARAMETER:
+ throw JSONRPCError(RPC_INVALID_PARAMETER, errors[0]);
break;
- case BumpFeeResult::WALLET_ERROR:
- throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
+ case feebumper::Result::WALLET_ERROR:
+ throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
break;
default:
- throw JSONRPCError(RPC_MISC_ERROR, feeBump.getErrors()[0]);
+ throw JSONRPCError(RPC_MISC_ERROR, errors[0]);
break;
}
}
// sign bumped transaction
- if (!feeBump.signTransaction(pwallet)) {
+ if (!feebumper::SignTransaction(pwallet, mtx)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
}
// commit the bumped transaction
- if(!feeBump.commit(pwallet)) {
- throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
+ uint256 txid;
+ if (feebumper::CommitTransaction(pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) {
+ throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
}
UniValue result(UniValue::VOBJ);
- result.push_back(Pair("txid", feeBump.getBumpedTxId().GetHex()));
- result.push_back(Pair("origfee", ValueFromAmount(feeBump.getOldFee())));
- result.push_back(Pair("fee", ValueFromAmount(feeBump.getNewFee())));
- UniValue errors(UniValue::VARR);
- for (const std::string& err: feeBump.getErrors())
- errors.push_back(err);
- result.push_back(Pair("errors", errors));
+ result.push_back(Pair("txid", txid.GetHex()));
+ result.push_back(Pair("origfee", ValueFromAmount(old_fee)));
+ result.push_back(Pair("fee", ValueFromAmount(new_fee)));
+ UniValue result_errors(UniValue::VARR);
+ for (const std::string& error : errors) {
+ result_errors.push_back(error);
+ }
+ result.push_back(Pair("errors", result_errors));
return result;
}
@@ -3180,6 +3438,91 @@ UniValue generate(const JSONRPCRequest& request)
return generateBlocks(coinbase_script, num_generate, max_tries, true);
}
+UniValue rescanblockchain(const JSONRPCRequest& request)
+{
+ CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 2) {
+ throw std::runtime_error(
+ "rescanblockchain (\"start_height\") (\"stop_height\")\n"
+ "\nRescan the local blockchain for wallet related transactions.\n"
+ "\nArguments:\n"
+ "1. \"start_height\" (numeric, optional) block height where the rescan should start\n"
+ "2. \"stop_height\" (numeric, optional) the last block height that should be scanned\n"
+ "\nResult:\n"
+ "{\n"
+ " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n"
+ " \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n"
+ "}\n"
+ "\nExamples:\n"
+ + HelpExampleCli("rescanblockchain", "100000 120000")
+ + HelpExampleRpc("rescanblockchain", "100000, 120000")
+ );
+ }
+
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ CBlockIndex *pindexStart = nullptr;
+ CBlockIndex *pindexStop = nullptr;
+ CBlockIndex *pChainTip = nullptr;
+ {
+ LOCK(cs_main);
+ pindexStart = chainActive.Genesis();
+ pChainTip = chainActive.Tip();
+
+ if (!request.params[0].isNull()) {
+ pindexStart = chainActive[request.params[0].get_int()];
+ if (!pindexStart) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
+ }
+ }
+
+ if (!request.params[1].isNull()) {
+ pindexStop = chainActive[request.params[1].get_int()];
+ if (!pindexStop) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
+ }
+ else if (pindexStop->nHeight < pindexStart->nHeight) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then start_height");
+ }
+ }
+ }
+
+ // We can't rescan beyond non-pruned blocks, stop and throw an error
+ if (fPruneMode) {
+ LOCK(cs_main);
+ CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
+ while (block && block->nHeight >= pindexStart->nHeight) {
+ if (!(block->nStatus & BLOCK_HAVE_DATA)) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
+ }
+ block = block->pprev;
+ }
+ }
+
+ CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, true);
+ if (!stopBlock) {
+ if (pwallet->IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
+ }
+ // if we got a nullptr returned, ScanForWalletTransactions did rescan up to the requested stopindex
+ stopBlock = pindexStop ? pindexStop : pChainTip;
+ }
+ else {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
+ }
+ UniValue response(UniValue::VOBJ);
+ response.pushKV("start_height", pindexStart->nHeight);
+ response.pushKV("stop_height", stopBlock->nHeight);
+ return response;
+}
+
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue importprivkey(const JSONRPCRequest& request);
@@ -3190,16 +3533,17 @@ extern UniValue importwallet(const JSONRPCRequest& request);
extern UniValue importprunedfunds(const JSONRPCRequest& request);
extern UniValue removeprunedfunds(const JSONRPCRequest& request);
extern UniValue importmulti(const JSONRPCRequest& request);
+extern UniValue rescanblockchain(const JSONRPCRequest& request);
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options"} },
+ { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
{ "wallet", "abortrescan", &abortrescan, {} },
- { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account"} },
- { "wallet", "addwitnessaddress", &addwitnessaddress, {"address"} },
+ { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account","address_type"} },
+ { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
@@ -3209,8 +3553,8 @@ static const CRPCCommand commands[] =
{ "wallet", "getaccount", &getaccount, {"address"} },
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
- { "wallet", "getnewaddress", &getnewaddress, {"account"} },
- { "wallet", "getrawchangeaddress", &getrawchangeaddress, {} },
+ { "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} },
+ { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
@@ -3244,6 +3588,7 @@ static const CRPCCommand commands[] =
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
+ { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
};
diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h
index 14e51610d9..77f7b42b23 100644
--- a/src/wallet/rpcwallet.h
+++ b/src/wallet/rpcwallet.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-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.
diff --git a/src/wallet/test/accounting_tests.cpp b/src/wallet/test/accounting_tests.cpp
index 330878ceb5..cafd69d075 100644
--- a/src/wallet/test/accounting_tests.cpp
+++ b/src/wallet/test/accounting_tests.cpp
@@ -1,27 +1,25 @@
-// Copyright (c) 2012-2016 The Bitcoin Core developers
+// Copyright (c) 2012-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 <wallet/wallet.h>
-#include "wallet/test/wallet_test_fixture.h"
+#include <wallet/test/wallet_test_fixture.h>
#include <stdint.h>
#include <boost/test/unit_test.hpp>
-extern CWallet* pwalletMain;
-
BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup)
static void
-GetResults(std::map<CAmount, CAccountingEntry>& results)
+GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results)
{
std::list<CAccountingEntry> aes;
results.clear();
- BOOST_CHECK(pwalletMain->ReorderTransactions() == DB_LOAD_OK);
- pwalletMain->ListAccountCreditDebit("", aes);
+ BOOST_CHECK(wallet->ReorderTransactions() == DB_LOAD_OK);
+ wallet->ListAccountCreditDebit("", aes);
for (CAccountingEntry& ae : aes)
{
results[ae.nOrderPos] = ae;
@@ -54,7 +52,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.strOtherAccount = "c";
pwalletMain->AddAccountingEntry(ae);
- GetResults(results);
+ GetResults(pwalletMain.get(), results);
BOOST_CHECK(pwalletMain->nOrderPosNext == 3);
BOOST_CHECK(2 == results.size());
@@ -70,7 +68,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nOrderPos = pwalletMain->IncOrderPosNext();
pwalletMain->AddAccountingEntry(ae);
- GetResults(results);
+ GetResults(pwalletMain.get(), results);
BOOST_CHECK(results.size() == 3);
BOOST_CHECK(pwalletMain->nOrderPosNext == 4);
@@ -83,7 +81,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
wtx.mapValue["comment"] = "y";
{
- CMutableTransaction tx(wtx);
+ CMutableTransaction tx(*wtx.tx);
--tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
@@ -93,7 +91,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
wtx.mapValue["comment"] = "x";
{
- CMutableTransaction tx(wtx);
+ CMutableTransaction tx(*wtx.tx);
--tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
@@ -102,7 +100,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
vpwtx[2]->nTimeReceived = (unsigned int)1333333329;
vpwtx[2]->nOrderPos = -1;
- GetResults(results);
+ GetResults(pwalletMain.get(), results);
BOOST_CHECK(results.size() == 3);
BOOST_CHECK(pwalletMain->nOrderPosNext == 6);
@@ -120,7 +118,7 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nOrderPos = -1;
pwalletMain->AddAccountingEntry(ae);
- GetResults(results);
+ GetResults(pwalletMain.get(), results);
BOOST_CHECK(results.size() == 4);
BOOST_CHECK(pwalletMain->nOrderPosNext == 7);
diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp
index f4dabc50c0..89b2c4e796 100644
--- a/src/wallet/test/crypto_tests.cpp
+++ b/src/wallet/test/crypto_tests.cpp
@@ -1,10 +1,10 @@
-// Copyright (c) 2014-2016 The Bitcoin Core developers
+// Copyright (c) 2014-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 "test/test_bitcoin.h"
-#include "utilstrencodings.h"
-#include "wallet/crypter.h"
+#include <test/test_bitcoin.h>
+#include <utilstrencodings.h>
+#include <wallet/crypter.h>
#include <vector>
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index e2f48c45ab..7797f85f07 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -1,14 +1,11 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-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/test/wallet_test_fixture.h"
+#include <wallet/test/wallet_test_fixture.h>
-#include "rpc/server.h"
-#include "wallet/db.h"
-#include "wallet/wallet.h"
-
-CWallet *pwalletMain;
+#include <rpc/server.h>
+#include <wallet/db.h>
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
TestingSetup(chainName)
@@ -16,19 +13,19 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
bitdb.MakeMock();
bool fFirstRun;
+ g_address_type = OUTPUT_TYPE_DEFAULT;
+ g_change_type = OUTPUT_TYPE_DEFAULT;
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
- pwalletMain = new CWallet(std::move(dbw));
+ pwalletMain = MakeUnique<CWallet>(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
- RegisterValidationInterface(pwalletMain);
+ RegisterValidationInterface(pwalletMain.get());
RegisterWalletRPCCommands(tableRPC);
}
WalletTestingSetup::~WalletTestingSetup()
{
- UnregisterValidationInterface(pwalletMain);
- delete pwalletMain;
- pwalletMain = nullptr;
+ UnregisterValidationInterface(pwalletMain.get());
bitdb.Flush(true);
bitdb.Reset();
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index 9373b7907c..c03aec7f87 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -1,17 +1,21 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-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.
#ifndef BITCOIN_WALLET_TEST_FIXTURE_H
#define BITCOIN_WALLET_TEST_FIXTURE_H
-#include "test/test_bitcoin.h"
+#include <test/test_bitcoin.h>
+
+#include <wallet/wallet.h>
/** Testing setup and teardown for wallet.
*/
struct WalletTestingSetup: public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~WalletTestingSetup();
+
+ std::unique_ptr<CWallet> pwalletMain;
};
#endif
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 5ebacd57d3..f39471b871 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -1,26 +1,24 @@
-// Copyright (c) 2012-2016 The Bitcoin Core developers
+// Copyright (c) 2012-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 <wallet/wallet.h>
#include <set>
#include <stdint.h>
#include <utility>
#include <vector>
-#include "consensus/validation.h"
-#include "rpc/server.h"
-#include "test/test_bitcoin.h"
-#include "validation.h"
-#include "wallet/coincontrol.h"
-#include "wallet/test/wallet_test_fixture.h"
+#include <consensus/validation.h>
+#include <rpc/server.h>
+#include <test/test_bitcoin.h>
+#include <validation.h>
+#include <wallet/coincontrol.h>
+#include <wallet/test/wallet_test_fixture.h>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
-extern CWallet* pwalletMain;
-
extern UniValue importmulti(const JSONRPCRequest& request);
extern UniValue dumpwallet(const JSONRPCRequest& request);
extern UniValue importwallet(const JSONRPCRequest& request);
@@ -105,7 +103,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
// we can't make 3 cents of mature coins
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, 1, 6, 0, vCoins, setCoinsRet, nValueRet));
- // we can make 3 cents of new coins
+ // we can make 3 cents of new coins
BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, 1, 1, 0, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
@@ -372,8 +370,6 @@ static void AddKey(CWallet& wallet, const CKey& key)
BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
- LOCK(cs_main);
-
// Cap last block file size, and mine new block in a new block file.
CBlockIndex* const nullBlock = nullptr;
CBlockIndex* oldTip = chainActive.Tip();
@@ -381,12 +377,16 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = chainActive.Tip();
+ LOCK(cs_main);
+
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet;
AddKey(wallet, coinbaseKey);
- BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
}
@@ -399,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
CWallet wallet;
AddKey(wallet, coinbaseKey);
- BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
}
@@ -449,8 +451,6 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
- LOCK(cs_main);
-
// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
const int64_t BLOCK_TIME = chainActive.Tip()->GetBlockTimeMax() + 5;
@@ -464,6 +464,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
coinbaseTxns.emplace_back(*CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ LOCK(cs_main);
+
// Import key into wallet and call dumpwallet to create backup file.
{
CWallet wallet;
@@ -489,6 +491,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
vpwallets[0] = &wallet;
::importwallet(request);
+ LOCK(wallet.cs_wallet);
BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3);
BOOST_CHECK_EQUAL(coinbaseTxns.size(), 103);
for (size_t i = 0; i < coinbaseTxns.size(); ++i) {
@@ -534,6 +537,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
SetMockTime(mockTime);
CBlockIndex* block = nullptr;
if (blockTime > 0) {
+ LOCK(cs_main);
auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex);
assert(inserted.second);
const uint256& hash = inserted.first->first;
@@ -547,6 +551,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
wtx.SetMerkleBranch(block, 0);
}
wallet.AddToWallet(wtx);
+ LOCK(wallet.cs_wallet);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
}
@@ -583,6 +588,7 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
CTxDestination dest = CKeyID();
+ LOCK(pwalletMain->cs_wallet);
pwalletMain->AddDestData(dest, "misc", "val_misc");
pwalletMain->AddDestData(dest, "rr0", "val_rr0");
pwalletMain->AddDestData(dest, "rr1", "val_rr1");
@@ -600,11 +606,15 @@ public:
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
::bitdb.MakeMock();
+ g_address_type = OUTPUT_TYPE_DEFAULT;
+ g_change_type = OUTPUT_TYPE_DEFAULT;
wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
- wallet->ScanForWalletTransactions(chainActive.Genesis());
+ WalletRescanReserver reserver(wallet.get());
+ reserver.reserve();
+ wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
}
~ListCoinsTestingSetup()
@@ -625,9 +635,15 @@ public:
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy));
CValidationState state;
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
+ CMutableTransaction blocktx;
+ {
+ LOCK(wallet->cs_wallet);
+ blocktx = CMutableTransaction(*wallet->mapWallet.at(wtx.GetHash()).tx);
+ }
+ CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
+ LOCK(wallet->cs_wallet);
auto it = wallet->mapWallet.find(wtx.GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
- CreateAndProcessBlock({CMutableTransaction(*it->second.tx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
it->second.SetMerkleBranch(chainActive.Tip(), 1);
return it->second;
}
@@ -638,7 +654,6 @@ public:
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
- LOCK2(cs_main, wallet->cs_wallet);
// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
// address.
@@ -666,6 +681,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(available.size(), 2);
for (const auto& group : list) {
for (const auto& coin : group.second) {
+ LOCK(wallet->cs_wallet);
wallet->LockCoin(COutPoint(coin.tx->GetHash(), coin.i));
}
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index fd2c1dfbe7..2b8019395c 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,38 +1,37 @@
// 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/fees.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>
@@ -43,6 +42,8 @@ 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;
const char * DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
@@ -111,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
@@ -264,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);
@@ -272,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);
@@ -320,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);
@@ -328,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);
}
@@ -390,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;
@@ -503,16 +531,16 @@ 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)
{
@@ -596,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;
@@ -682,9 +710,9 @@ 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);
+ CWalletTx* wtx = &entry.second;
txByTime.insert(std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr)));
}
std::list<CAccountingEntry> acentries;
@@ -792,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);
@@ -803,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)
@@ -821,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;
}
@@ -915,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
@@ -934,7 +973,7 @@ 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
@@ -946,9 +985,7 @@ 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, nullptr)));
AddToSpends(hash);
@@ -1186,6 +1223,19 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin
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) {
@@ -1200,10 +1250,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) {
@@ -1216,6 +1270,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
{
{
@@ -1528,19 +1607,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;
}
@@ -1556,47 +1636,86 @@ 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);
+ }
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) {
+ double gvp = 0;
+ {
+ LOCK(cs_main);
+ gvp = GuessVerificationProgress(chainParams.TxData(), pindex);
+ }
+ ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((gvp - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ }
if (GetTime() >= nNow + 60) {
nNow = GetTime();
+ LOCK(cs_main);
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
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);
+ 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));
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
-
- fScanningWallet = false;
}
return ret;
}
@@ -1624,11 +1743,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);
}
@@ -1680,7 +1796,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;
}
@@ -1691,7 +1807,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;
}
@@ -1713,7 +1829,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;
}
@@ -1724,7 +1840,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;
}
@@ -1738,7 +1854,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;
}
@@ -1776,13 +1892,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;
}
@@ -1790,7 +1906,7 @@ 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 == nullptr)
return 0;
@@ -1823,21 +1939,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)
@@ -1938,9 +2053,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();
}
@@ -1954,9 +2069,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();
}
@@ -1969,9 +2084,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();
}
}
@@ -1983,9 +2098,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();
}
@@ -1999,9 +2114,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();
}
@@ -2014,9 +2129,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();
}
}
@@ -2081,7 +2196,7 @@ 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
{
vCoins.clear();
@@ -2090,12 +2205,12 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
CAmount nTotal = 0;
- for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
+ for (const auto& entry : mapWallet)
{
- const uint256& wtxid = it->first;
- const CWalletTx* pcoin = &(*it).second;
+ const uint256& wtxid = entry.first;
+ const CWalletTx* pcoin = &entry.second;
- if (!CheckFinalTx(*pcoin))
+ if (!CheckFinalTx(*pcoin->tx))
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
@@ -2154,10 +2269,10 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
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)))
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue;
- if (IsLockedCoin((*it).first, i))
+ if (IsLockedCoin(entry.first, i))
continue;
if (IsSpent(wtxid, i))
@@ -2504,9 +2619,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);
@@ -2514,8 +2628,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;
@@ -2525,34 +2644,59 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]);
- // 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.
+ // 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;
}
+OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend)
+{
+ // 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,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{
@@ -2648,7 +2792,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);
@@ -2676,6 +2823,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
@@ -2876,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;
@@ -2938,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)) {
+ if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
LogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", state.GetRejectReason());
// 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);
}
}
}
@@ -3532,6 +3684,7 @@ 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);
@@ -3612,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
@@ -3634,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
}
/**
@@ -3764,7 +3917,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
- std::unique_ptr<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));
@@ -3842,7 +3995,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
// Top up the keypool
if (!walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys") += "\n");
- return NULL;
+ return nullptr;
}
walletInstance->SetBestChain(chainActive.GetLocator());
@@ -3861,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();
@@ -3874,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
@@ -3901,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();
@@ -4017,7 +4179,123 @@ int CMerkleTx::GetBlocksToMaturity() const
}
-bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
+bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state)
{
- return ::AcceptToMemoryPool(mempool, state, tx, true, nullptr, nullptr, false, nAbsurdFee);
+ // Quick check to avoid re-setting fInMempool to false
+ if (mempool.exists(tx->GetHash())) {
+ return false;
+ }
+
+ // 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)
+{
+ 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);
+ }
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cd38d279ed..fefe415bb1 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,23 +1,23 @@
// 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.
#ifndef BITCOIN_WALLET_WALLET_H
#define BITCOIN_WALLET_WALLET_H
-#include "amount.h"
-#include "policy/feerate.h"
-#include "streams.h"
-#include "tinyformat.h"
-#include "ui_interface.h"
-#include "utilstrencodings.h"
-#include "validationinterface.h"
-#include "script/ismine.h"
-#include "script/sign.h"
-#include "wallet/crypter.h"
-#include "wallet/walletdb.h"
-#include "wallet/rpcwallet.h"
+#include <amount.h>
+#include <policy/feerate.h>
+#include <streams.h>
+#include <tinyformat.h>
+#include <ui_interface.h>
+#include <utilstrencodings.h>
+#include <validationinterface.h>
+#include <script/ismine.h>
+#include <script/sign.h>
+#include <wallet/crypter.h>
+#include <wallet/walletdb.h>
+#include <wallet/rpcwallet.h>
#include <algorithm>
#include <atomic>
@@ -99,6 +99,19 @@ enum WalletFeature
FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
};
+enum OutputType : int
+{
+ OUTPUT_TYPE_NONE,
+ OUTPUT_TYPE_LEGACY,
+ OUTPUT_TYPE_P2SH_SEGWIT,
+ OUTPUT_TYPE_BECH32,
+
+ OUTPUT_TYPE_DEFAULT = OUTPUT_TYPE_P2SH_SEGWIT
+};
+
+extern OutputType g_address_type;
+extern OutputType g_change_type;
+
/** A key pool entry */
class CKeyPool
@@ -214,10 +227,6 @@ public:
Init();
}
- /** Helper conversion operator to allow passing CMerkleTx where CTransaction is expected.
- * TODO: adapt callers and remove this operator. */
- operator const CTransaction&() const { return *tx; }
-
void Init()
{
hashBlock = uint256();
@@ -252,8 +261,6 @@ public:
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
int GetBlocksToMaturity() const;
- /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
- bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
void setAbandoned() { hashBlock = ABANDON_HASH; }
@@ -330,6 +337,7 @@ public:
mutable bool fImmatureWatchCreditCached;
mutable bool fAvailableWatchCreditCached;
mutable bool fChangeCached;
+ mutable bool fInMempool;
mutable CAmount nDebitCached;
mutable CAmount nCreditCached;
mutable CAmount nImmatureCreditCached;
@@ -369,6 +377,7 @@ public:
fImmatureWatchCreditCached = false;
fAvailableWatchCreditCached = false;
fChangeCached = false;
+ fInMempool = false;
nDebitCached = 0;
nCreditCached = 0;
nImmatureCreditCached = 0;
@@ -449,8 +458,8 @@ public:
CAmount GetCredit(const isminefilter& filter) const;
CAmount GetImmatureCredit(bool fUseCache=true) const;
CAmount GetAvailableCredit(bool fUseCache=true) const;
- CAmount GetImmatureWatchOnlyCredit(const bool& fUseCache=true) const;
- CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const;
+ CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const;
+ CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const;
CAmount GetChange() const;
void GetAmounts(std::list<COutputEntry>& listReceived,
@@ -473,6 +482,9 @@ public:
// RelayWalletTransaction may only be called if fBroadcastTransactions!
bool RelayWalletTransaction(CConnman* connman);
+ /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
+ bool AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& state);
+
std::set<uint256> GetConflicts() const;
};
@@ -647,6 +659,7 @@ private:
};
+class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime
/**
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
* and provides the ability to create new transactions.
@@ -656,7 +669,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
private:
static std::atomic<bool> fFlushScheduled;
std::atomic<bool> fAbortRescan;
- std::atomic<bool> fScanningWallet;
+ std::atomic<bool> fScanningWallet; //controlled by WalletRescanReserver
+ std::mutex mutexScanning;
+ friend class WalletRescanReserver;
+
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
@@ -722,6 +738,18 @@ private:
std::unique_ptr<CWalletDBWrapper> dbw;
+ /**
+ * The following is used to keep track of how far behind the wallet is
+ * from the chain sync, and to allow clients to block on us being caught up.
+ *
+ * Note that this is *not* how far we've processed, we may need some rescan
+ * to have seen all transactions in the chain, but is only used to track
+ * live BlockConnected callbacks.
+ *
+ * Protected by cs_main (see BlockUntilSyncedToCurrentChain)
+ */
+ const CBlockIndex* m_last_block_processed;
+
public:
/*
* Main wallet lock.
@@ -750,9 +778,11 @@ public:
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool);
- // Map from Key ID (for regular keys) or Script ID (for watch-only keys) to
- // key metadata.
- std::map<CTxDestination, CKeyMetadata> mapKeyMetadata;
+ // Map from Key ID to key metadata.
+ std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
+
+ // Map from Script ID to key metadata (for watch-only keys).
+ std::map<CScriptID, CKeyMetadata> m_script_metadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
@@ -817,7 +847,7 @@ public:
/**
* populate vCoins with vector of available COutputs.
*/
- void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t& nMaximumCount = 0, const int& nMinDepth = 0, const int& nMaxDepth = 9999999) const;
+ void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe=true, const CCoinControl *coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const;
/**
* Return list of available coins and locked coins grouped by non-change output address.
@@ -863,7 +893,8 @@ public:
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); }
//! Load metadata (used by LoadWallet)
- bool LoadKeyMetadata(const CTxDestination& pubKey, const CKeyMetadata &metadata);
+ bool LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
+ bool LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
void UpdateTimeFirstKey(int64_t nCreateTime);
@@ -909,7 +940,7 @@ public:
int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr);
DBErrors ReorderTransactions();
bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "");
- bool GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew = false);
+ bool GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew = false);
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
@@ -918,8 +949,9 @@ public:
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
- int64_t RescanFromTime(int64_t startTime, bool update);
- CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
+ int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
+ CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, CBlockIndex* pindexStop, const WalletRescanReserver& reserver, bool fUpdate = false);
+ void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
@@ -933,6 +965,8 @@ public:
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
+ OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend);
+
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
@@ -1106,6 +1140,34 @@ public:
caller must ensure the current wallet version is correct before calling
this function). */
bool SetHDMasterKey(const CPubKey& key);
+
+ /**
+ * Blocks until the wallet state is up-to-date to /at least/ the current
+ * chain at the time this function is entered
+ * Obviously holding cs_main/cs_wallet when going into this call may cause
+ * deadlock
+ */
+ void BlockUntilSyncedToCurrentChain();
+
+ /**
+ * Explicitly make the wallet learn the related scripts for outputs to the
+ * given key. This is purely to make the wallet file compatible with older
+ * software, as CBasicKeyStore automatically does this implicitly for all
+ * keys now.
+ */
+ void LearnRelatedScripts(const CPubKey& key, OutputType);
+
+ /**
+ * Same as LearnRelatedScripts, but when the OutputType is not known (and could
+ * be anything).
+ */
+ void LearnAllRelatedScripts(const CPubKey& key);
+
+ /**
+ * Get a destination of the requested type (if possible) to the specified script.
+ * This function will automatically add the necessary scripts to the wallet.
+ */
+ CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType);
};
/** A key allocated from the key pool. */
@@ -1195,4 +1257,51 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
return true;
}
+OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT);
+const std::string& FormatOutputType(OutputType type);
+
+/**
+ * Get a destination of the requested type (if possible) to the specified key.
+ * The caller must make sure LearnRelatedScripts has been called beforehand.
+ */
+CTxDestination GetDestinationForKey(const CPubKey& key, OutputType);
+
+/** Get all destinations (potentially) supported by the wallet for the given key. */
+std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
+
+/** RAII object to check and reserve a wallet rescan */
+class WalletRescanReserver
+{
+private:
+ CWalletRef m_wallet;
+ bool m_could_reserve;
+public:
+ explicit WalletRescanReserver(CWalletRef w) : m_wallet(w), m_could_reserve(false) {}
+
+ bool reserve()
+ {
+ assert(!m_could_reserve);
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_wallet->fScanningWallet) {
+ return false;
+ }
+ m_wallet->fScanningWallet = true;
+ m_could_reserve = true;
+ return true;
+ }
+
+ bool isReserved() const
+ {
+ return (m_could_reserve && m_wallet->fScanningWallet);
+ }
+
+ ~WalletRescanReserver()
+ {
+ std::lock_guard<std::mutex> lock(m_wallet->mutexScanning);
+ if (m_could_reserve) {
+ m_wallet->fScanningWallet = false;
+ }
+ }
+};
+
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 52370a8eb5..dd6835a06f 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,20 +1,20 @@
// 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/walletdb.h"
+#include <wallet/walletdb.h>
-#include "base58.h"
-#include "consensus/tx_verify.h"
-#include "consensus/validation.h"
-#include "fs.h"
-#include "protocol.h"
-#include "serialize.h"
-#include "sync.h"
-#include "util.h"
-#include "utiltime.h"
-#include "wallet/wallet.h"
+#include <base58.h>
+#include <consensus/tx_verify.h>
+#include <consensus/validation.h>
+#include <fs.h>
+#include <protocol.h>
+#include <serialize.h>
+#include <sync.h>
+#include <util.h>
+#include <utiltime.h>
+#include <wallet/wallet.h>
#include <atomic>
@@ -41,9 +41,9 @@ bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& s
return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose);
}
-bool CWalletDB::ErasePurpose(const std::string& strPurpose)
+bool CWalletDB::ErasePurpose(const std::string& strAddress)
{
- return EraseIC(std::make_pair(std::string("purpose"), strPurpose));
+ return EraseIC(std::make_pair(std::string("purpose"), strAddress));
}
bool CWalletDB::WriteTx(const CWalletTx& wtx)
@@ -268,7 +268,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CWalletTx wtx;
ssValue >> wtx;
CValidationState state;
- if (!(CheckTransaction(wtx, state) && (wtx.GetHash() == hash) && state.IsValid()))
+ if (!(CheckTransaction(*wtx.tx, state) && (wtx.GetHash() == hash) && state.IsValid()))
return false;
// Undo serialize changes in 31600
@@ -423,27 +423,23 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
wss.fIsEncrypted = true;
}
- else if (strType == "keymeta" || strType == "watchmeta")
+ else if (strType == "keymeta")
{
- CTxDestination keyID;
- if (strType == "keymeta")
- {
- CPubKey vchPubKey;
- ssKey >> vchPubKey;
- keyID = vchPubKey.GetID();
- }
- else if (strType == "watchmeta")
- {
- CScript script;
- ssKey >> script;
- keyID = CScriptID(script);
- }
-
+ CPubKey vchPubKey;
+ ssKey >> vchPubKey;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
-
- pwallet->LoadKeyMetadata(keyID, keyMeta);
+ pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
+ }
+ else if (strType == "watchmeta")
+ {
+ CScript script;
+ ssKey >> script;
+ CKeyMetadata keyMeta;
+ ssValue >> keyMeta;
+ wss.nKeyMeta++;
+ pwallet->LoadScriptMetadata(CScriptID(script), keyMeta);
}
else if (strType == "defaultkey")
{
@@ -630,7 +626,6 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx)
{
- bool fNoncriticalErrors = false;
DBErrors result = DB_LOAD_OK;
try {
@@ -685,9 +680,6 @@ DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWal
result = DB_CORRUPT;
}
- if (fNoncriticalErrors && result == DB_LOAD_OK)
- result = DB_NONCRITICAL_ERROR;
-
return result;
}
@@ -814,14 +806,14 @@ bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDa
return true;
}
-bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr)
+bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
{
- return CDB::VerifyEnvironment(walletFile, dataDir, errorStr);
+ return CDB::VerifyEnvironment(walletFile, walletDir, errorStr);
}
-bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr)
+bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr)
{
- return CDB::VerifyDatabaseFile(walletFile, dataDir, warningStr, errorStr, CWalletDB::Recover);
+ return CDB::VerifyDatabaseFile(walletFile, walletDir, warningStr, errorStr, CWalletDB::Recover);
}
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 4f8ea185d5..3691cfcb57 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,15 +1,15 @@
// 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.
#ifndef BITCOIN_WALLET_WALLETDB_H
#define BITCOIN_WALLET_WALLETDB_H
-#include "amount.h"
-#include "primitives/transaction.h"
-#include "wallet/db.h"
-#include "key.h"
+#include <amount.h>
+#include <primitives/transaction.h>
+#include <wallet/db.h>
+#include <key.h>
#include <list>
#include <stdint.h>
@@ -167,6 +167,8 @@ public:
m_dbw(dbw)
{
}
+ CWalletDB(const CWalletDB&) = delete;
+ CWalletDB& operator=(const CWalletDB&) = delete;
bool WriteName(const std::string& strAddress, const std::string& strName);
bool EraseName(const std::string& strAddress);
@@ -224,9 +226,9 @@ public:
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
/* verifies the database environment */
- static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr);
+ static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr);
/* verifies the database file */
- static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr);
+ static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
@@ -244,9 +246,6 @@ public:
private:
CDB batch;
CWalletDBWrapper& m_dbw;
-
- CWalletDB(const CWalletDB&);
- void operator=(const CWalletDB&);
};
//! Compacts BDB state so that wallet.dat is self-contained (if there are changes)
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
new file mode 100644
index 0000000000..7c97b668ae
--- /dev/null
+++ b/src/wallet/walletutil.cpp
@@ -0,0 +1,27 @@
+// Copyright (c) 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/walletutil.h>
+
+fs::path GetWalletDir()
+{
+ fs::path path;
+
+ if (gArgs.IsArgSet("-walletdir")) {
+ path = gArgs.GetArg("-walletdir", "");
+ if (!fs::is_directory(path)) {
+ // If the path specified doesn't exist, we return the deliberately
+ // invalid empty string.
+ path = "";
+ }
+ } else {
+ path = GetDataDir();
+ // If a wallets directory exists, use that, otherwise default to GetDataDir
+ if (fs::is_directory(path / "wallets")) {
+ path /= "wallets";
+ }
+ }
+
+ return path;
+}
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
new file mode 100644
index 0000000000..50ff736402
--- /dev/null
+++ b/src/wallet/walletutil.h
@@ -0,0 +1,14 @@
+// Copyright (c) 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.
+
+#ifndef BITCOIN_WALLET_UTIL_H
+#define BITCOIN_WALLET_UTIL_H
+
+#include <chainparamsbase.h>
+#include <util.h>
+
+//! Get the path of the wallet directory.
+fs::path GetWalletDir();
+
+#endif // BITCOIN_WALLET_UTIL_H