aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.h6
-rw-r--r--src/wallet/crypter.cpp3
-rw-r--r--src/wallet/crypter.h2
-rw-r--r--src/wallet/db.cpp51
-rw-r--r--src/wallet/db.h4
-rw-r--r--src/wallet/feebumper.cpp45
-rw-r--r--src/wallet/feebumper.h2
-rw-r--r--src/wallet/init.cpp33
-rw-r--r--src/wallet/rpcdump.cpp422
-rw-r--r--src/wallet/rpcwallet.cpp454
-rw-r--r--src/wallet/rpcwallet.h2
-rw-r--r--src/wallet/test/accounting_tests.cpp2
-rw-r--r--src/wallet/test/crypto_tests.cpp2
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp4
-rw-r--r--src/wallet/test/wallet_test_fixture.h2
-rw-r--r--src/wallet/test/wallet_tests.cpp54
-rw-r--r--src/wallet/wallet.cpp486
-rw-r--r--src/wallet/wallet.h115
-rw-r--r--src/wallet/walletdb.cpp36
-rw-r--r--src/wallet/walletdb.h2
-rw-r--r--src/wallet/walletutil.cpp4
-rw-r--r--src/wallet/walletutil.h3
22 files changed, 1118 insertions, 616 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h
index 15fd105779..458e770e03 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -1,4 +1,4 @@
-// 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.
@@ -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 4cd7db048b..6ad18721fd 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -1,4 +1,4 @@
-// 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.
@@ -245,6 +245,7 @@ bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<
}
mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ ImplicitlyLearnRelatedKeyScripts(vchPubKey);
return true;
}
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index 7b0936ba0d..f3ae7144b4 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,4 +1,4 @@
-// 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.
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 79ff27279c..23c6279128 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,12 +1,11 @@
// 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/db.h>
#include <addrman.h>
-#include <fs.h>
#include <hash.h>
#include <protocol.h>
#include <util.h>
@@ -96,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;
@@ -104,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";
@@ -135,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;
@@ -270,25 +291,11 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
return false;
}
- if (!bitdb.Open(walletDir))
- {
- // try moving the database env out of the way
- fs::path pathDatabase = walletDir / "database";
- fs::path pathDatabaseBak = walletDir / 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(walletDir)) {
- // if it still fails, it probably means we can't even create the database env
- errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
- return false;
- }
+ if (!bitdb.Open(walletDir, true)) {
+ errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
+ return false;
}
+
return true;
}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index ed2ee65cac..787135e400 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,5 +1,5 @@
// 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.
@@ -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);
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 9bfcab54a5..5234a69710 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -65,16 +65,39 @@ static feebumper::Result PreconditionChecks(const CWallet* wallet, const CWallet
errors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
return feebumper::Result::WALLET_ERROR;
}
+
+ if (!SignalsOptInRBF(*wtx.tx)) {
+ errors.push_back("Transaction is not BIP 125 replaceable");
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ errors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid")));
+ return feebumper::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 (!wallet->IsAllFromMe(*wtx.tx, ISMINE_SPENDABLE)) {
+ errors.push_back("Transaction contains inputs that don't belong to this wallet");
+ return feebumper::Result::WALLET_ERROR;
+ }
+
+
return feebumper::Result::OK;
}
namespace feebumper {
-bool TransactionCanBeBumped(CWallet* wallet, const uint256& txid)
+bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid)
{
LOCK2(cs_main, wallet->cs_wallet);
const CWalletTx* wtx = wallet->GetWalletTx(txid);
- return wtx && SignalsOptInRBF(*wtx->tx) && !wtx->mapValue.count("replaced_by_txid");
+ if (wtx == nullptr) return false;
+
+ std::vector<std::string> errors_dummy;
+ feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
+ return res == feebumper::Result::OK;
}
Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors,
@@ -94,23 +117,6 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
return result;
}
- if (!SignalsOptInRBF(*wtx.tx)) {
- errors.push_back("Transaction is not BIP 125 replaceable");
- return Result::WALLET_ERROR;
- }
-
- if (wtx.mapValue.count("replaced_by_txid")) {
- 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 (!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;
@@ -228,6 +234,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin
}
}
+
return Result::OK;
}
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
index 8eec30440c..7e36a9766b 100644
--- a/src/wallet/feebumper.h
+++ b/src/wallet/feebumper.h
@@ -26,7 +26,7 @@ enum class Result
};
//! Return whether transaction can be bumped.
-bool TransactionCanBeBumped(CWallet* wallet, const uint256& txid);
+bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid);
//! Create bumpfee transaction.
Result CreateTransaction(const CWallet* wallet,
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 67c46df87d..74036f4f0f 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -16,13 +16,15 @@
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)"),
@@ -31,12 +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)"));
@@ -175,6 +177,18 @@ 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;
}
@@ -193,8 +207,15 @@ bool VerifyWallets()
return true;
}
- if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) {
- return InitError(strprintf(_("Error: Specified wallet directory \"%s\" does not exist."), gArgs.GetArg("-walletdir", "").c_str()));
+ 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());
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 71d50be634..741ea25340 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,4 +1,4 @@
-// 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.
@@ -71,6 +71,28 @@ std::string DecodeDumpString(const std::string &str) {
return ret.str();
}
+bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel)
+{
+ bool fLabelFound = false;
+ CKey key;
+ pwallet->GetKey(keyid, key);
+ for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) {
+ if (pwallet->mapAddressBook.count(dest)) {
+ if (!strAddr.empty()) {
+ strAddr += ",";
+ }
+ strAddr += EncodeDestination(dest);
+ strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
+ fLabelFound = true;
+ }
+ }
+ if (!fLabelFound) {
+ strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), g_address_type));
+ }
+ return fLabelFound;
+}
+
+
UniValue importprivkey(const JSONRPCRequest& request)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -86,7 +108,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
"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 +124,65 @@ UniValue importprivkey(const JSONRPCRequest& request)
);
- LOCK2(cs_main, pwallet->cs_wallet);
+ WalletRescanReserver reserver(pwallet);
+ bool fRescan = true;
+ {
+ LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked(pwallet);
+ EnsureWalletIsUnlocked(pwallet);
- std::string strSecret = request.params[0].get_str();
- std::string strLabel = "";
- if (!request.params[1].isNull())
- strLabel = request.params[1].get_str();
-
- // 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;
}
@@ -232,7 +264,8 @@ UniValue importaddress(const JSONRPCRequest& request)
"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 +291,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();
}
@@ -401,7 +440,8 @@ UniValue importpubkey(const JSONRPCRequest& request)
"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 +464,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 +476,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();
}
@@ -471,78 +520,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 +651,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();
@@ -601,7 +674,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
throw std::runtime_error(
"dumpwallet \"filename\"\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 not currently included in wallet dumps, these must be backed up separately.\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"
@@ -640,6 +713,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) {
@@ -657,7 +733,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())
{
@@ -675,12 +751,13 @@ UniValue dumpwallet(const JSONRPCRequest& request)
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
std::string strTime = EncodeDumpTime(it->first);
- std::string strAddr = EncodeDestination(keyid);
+ std::string strAddr;
+ std::string strLabel;
CKey key;
if (pwallet->GetKey(keyid, key)) {
file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime);
- if (pwallet->mapAddressBook.count(keyid)) {
- file << strprintf("label=%s", EncodeDumpString(pwallet->mapAddressBook[keyid].name));
+ if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) {
+ file << strprintf("label=%s", strLabel);
} else if (keyid == masterKeyID) {
file << "hdmaster=1";
} else if (mapKeyPool.count(keyid)) {
@@ -694,11 +771,26 @@ 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();
UniValue reply(UniValue::VOBJ);
- reply.push_back(Pair("filename", filepath.string()));
+ reply.pushKV("filename", filepath.string());
return reply;
}
@@ -721,8 +813,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;
@@ -1067,6 +1159,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 }]'") +
@@ -1092,49 +1186,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 473acd8367..c03f7d7923 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,5 +1,5 @@
// 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.
@@ -18,6 +18,7 @@
#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>
@@ -85,25 +86,25 @@ void EnsureWalletIsUnlocked(CWallet * const pwallet)
void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
{
int confirms = wtx.GetDepthInMainChain();
- entry.push_back(Pair("confirmations", confirms));
+ entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase())
- entry.push_back(Pair("generated", true));
+ entry.pushKV("generated", true);
if (confirms > 0)
{
- entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
- entry.push_back(Pair("blockindex", wtx.nIndex));
- entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
+ entry.pushKV("blockhash", wtx.hashBlock.GetHex());
+ entry.pushKV("blockindex", wtx.nIndex);
+ entry.pushKV("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime());
} else {
- entry.push_back(Pair("trusted", wtx.IsTrusted()));
+ entry.pushKV("trusted", wtx.IsTrusted());
}
uint256 hash = wtx.GetHash();
- entry.push_back(Pair("txid", hash.GetHex()));
+ entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR);
for (const uint256& conflict : wtx.GetConflicts())
conflicts.push_back(conflict.GetHex());
- entry.push_back(Pair("walletconflicts", conflicts));
- entry.push_back(Pair("time", wtx.GetTxTime()));
- entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived));
+ entry.pushKV("walletconflicts", conflicts);
+ entry.pushKV("time", wtx.GetTxTime());
+ entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived);
// Add opt-in RBF status
std::string rbfStatus = "no";
@@ -115,10 +116,10 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
else if (rbfState == RBF_TRANSACTIONSTATE_REPLACEABLE_BIP125)
rbfStatus = "yes";
}
- entry.push_back(Pair("bip125-replaceable", rbfStatus));
+ entry.pushKV("bip125-replaceable", rbfStatus);
for (const std::pair<std::string, std::string>& item : wtx.mapValue)
- entry.push_back(Pair(item.first, item.second));
+ entry.pushKV(item.first, item.second);
}
std::string AccountFromValue(const UniValue& value)
@@ -136,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"
@@ -158,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();
}
@@ -167,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)
@@ -214,7 +225,7 @@ UniValue getaccountaddress(const JSONRPCRequest& request)
UniValue ret(UniValue::VSTR);
- ret = EncodeDestination(GetAccountAddress(pwallet, strAccount));
+ ret = EncodeDestination(GetAccountDestination(pwallet, strAccount));
return ret;
}
@@ -226,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"
@@ -244,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))
@@ -251,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);
}
@@ -292,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");
@@ -771,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"
@@ -1036,7 +1060,7 @@ 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();
@@ -1138,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);
@@ -1148,25 +1169,29 @@ 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"
+ 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"
"\nExamples:\n"
"\nAdd a multisig address from 2 addresses\n"
+ HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
@@ -1182,13 +1207,37 @@ 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");
- 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>
@@ -1204,12 +1253,7 @@ public:
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;
}
return ExtractDestination(witscript, result);
@@ -1228,12 +1272,7 @@ public:
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;
}
return ExtractDestination(witscript, result);
@@ -1269,7 +1308,8 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
{
std::string msg = "addwitnessaddress \"address\" ( p2sh )\n"
- "\nAdd a witness address for a script (with pubkey or redeemscript known). Requires a new wallet backup.\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"
@@ -1283,6 +1323,12 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
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)) {
@@ -1317,7 +1363,7 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot convert between witness address types");
}
} else {
- pwallet->AddCScript(witprogram);
+ pwallet->AddCScript(witprogram); // Implicit for single-key now, but necessary for multisig and for compatibility with older software
pwallet->SetAddressBook(w.result, "", "receive");
}
@@ -1417,13 +1463,13 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
{
UniValue obj(UniValue::VOBJ);
if(fIsWatchonly)
- obj.push_back(Pair("involvesWatchonly", true));
- obj.push_back(Pair("address", EncodeDestination(dest)));
- obj.push_back(Pair("account", strAccount));
- obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
- obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
+ obj.pushKV("involvesWatchonly", true);
+ obj.pushKV("address", EncodeDestination(dest));
+ obj.pushKV("account", strAccount);
+ obj.pushKV("amount", ValueFromAmount(nAmount));
+ obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
if (!fByAccounts)
- obj.push_back(Pair("label", strAccount));
+ obj.pushKV("label", strAccount);
UniValue transactions(UniValue::VARR);
if (it != mapTally.end())
{
@@ -1432,7 +1478,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
transactions.push_back(_item.GetHex());
}
}
- obj.push_back(Pair("txids", transactions));
+ obj.pushKV("txids", transactions);
ret.push_back(obj);
}
}
@@ -1445,10 +1491,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
if (entry.second.fIsWatchonly)
- obj.push_back(Pair("involvesWatchonly", true));
- 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)));
+ obj.pushKV("involvesWatchonly", true);
+ obj.pushKV("account", entry.first);
+ obj.pushKV("amount", ValueFromAmount(nAmount));
+ obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
ret.push_back(obj);
}
}
@@ -1554,7 +1600,7 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request)
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
{
if (IsValidDestination(dest)) {
- entry.push_back(Pair("address", EncodeDestination(dest)));
+ entry.pushKV("address", EncodeDestination(dest));
}
}
@@ -1588,20 +1634,20 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s
{
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) {
- entry.push_back(Pair("involvesWatchonly", true));
+ entry.pushKV("involvesWatchonly", true);
}
- entry.push_back(Pair("account", strSentAccount));
+ entry.pushKV("account", strSentAccount);
MaybePushAddress(entry, s.destination);
- entry.push_back(Pair("category", "send"));
- entry.push_back(Pair("amount", ValueFromAmount(-s.amount)));
+ entry.pushKV("category", "send");
+ entry.pushKV("amount", ValueFromAmount(-s.amount));
if (pwallet->mapAddressBook.count(s.destination)) {
- entry.push_back(Pair("label", pwallet->mapAddressBook[s.destination].name));
+ entry.pushKV("label", pwallet->mapAddressBook[s.destination].name);
}
- entry.push_back(Pair("vout", s.vout));
- entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
+ entry.pushKV("vout", s.vout);
+ entry.pushKV("fee", ValueFromAmount(-nFee));
if (fLong)
WalletTxToJSON(wtx, entry);
- entry.push_back(Pair("abandoned", wtx.isAbandoned()));
+ entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry);
}
}
@@ -1619,28 +1665,28 @@ void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::s
{
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) {
- entry.push_back(Pair("involvesWatchonly", true));
+ entry.pushKV("involvesWatchonly", true);
}
- entry.push_back(Pair("account", account));
+ entry.pushKV("account", account);
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase())
{
if (wtx.GetDepthInMainChain() < 1)
- entry.push_back(Pair("category", "orphan"));
+ entry.pushKV("category", "orphan");
else if (wtx.GetBlocksToMaturity() > 0)
- entry.push_back(Pair("category", "immature"));
+ entry.pushKV("category", "immature");
else
- entry.push_back(Pair("category", "generate"));
+ entry.pushKV("category", "generate");
}
else
{
- entry.push_back(Pair("category", "receive"));
+ entry.pushKV("category", "receive");
}
- entry.push_back(Pair("amount", ValueFromAmount(r.amount)));
+ entry.pushKV("amount", ValueFromAmount(r.amount));
if (pwallet->mapAddressBook.count(r.destination)) {
- entry.push_back(Pair("label", account));
+ entry.pushKV("label", account);
}
- entry.push_back(Pair("vout", r.vout));
+ entry.pushKV("vout", r.vout);
if (fLong)
WalletTxToJSON(wtx, entry);
ret.push_back(entry);
@@ -1656,12 +1702,12 @@ void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccoun
if (fAllAccounts || acentry.strAccount == strAccount)
{
UniValue entry(UniValue::VOBJ);
- entry.push_back(Pair("account", acentry.strAccount));
- entry.push_back(Pair("category", "move"));
- entry.push_back(Pair("time", acentry.nTime));
- entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit)));
- entry.push_back(Pair("otheraccount", acentry.strOtherAccount));
- entry.push_back(Pair("comment", acentry.strComment));
+ entry.pushKV("account", acentry.strAccount);
+ entry.pushKV("category", "move");
+ entry.pushKV("time", acentry.nTime);
+ entry.pushKV("amount", ValueFromAmount(acentry.nCreditDebit));
+ entry.pushKV("otheraccount", acentry.strOtherAccount);
+ entry.pushKV("comment", acentry.strComment);
ret.push_back(entry);
}
}
@@ -1739,8 +1785,6 @@ UniValue listtransactions(const JSONRPCRequest& request)
// the user could have gotten from another RPC command prior to now
pwallet->BlockUntilSyncedToCurrentChain();
- LOCK2(cs_main, pwallet->cs_wallet);
-
std::string strAccount = "*";
if (!request.params[0].isNull())
strAccount = request.params[0].get_str();
@@ -1762,20 +1806,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);
- if ((int)ret.size() >= (nCount+nFrom)) break;
+ 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);
+
+ if ((int)ret.size() >= (nCount+nFrom)) break;
+ }
}
+
// ret is newest to oldest
if (nFrom > (int)ret.size())
@@ -1885,7 +1934,7 @@ UniValue listaccounts(const JSONRPCRequest& request)
UniValue ret(UniValue::VOBJ);
for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) {
- ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second)));
+ ret.pushKV(accountBalance.first, ValueFromAmount(accountBalance.second));
}
return ret;
}
@@ -2025,9 +2074,9 @@ UniValue listsinceblock(const JSONRPCRequest& request)
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256();
UniValue ret(UniValue::VOBJ);
- ret.push_back(Pair("transactions", transactions));
- if (include_removed) ret.push_back(Pair("removed", removed));
- ret.push_back(Pair("lastblock", lastblock.GetHex()));
+ ret.pushKV("transactions", transactions);
+ if (include_removed) ret.pushKV("removed", removed);
+ ret.pushKV("lastblock", lastblock.GetHex());
return ret;
}
@@ -2112,18 +2161,18 @@ UniValue gettransaction(const JSONRPCRequest& request)
CAmount nNet = nCredit - nDebit;
CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0);
- entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee)));
+ entry.pushKV("amount", ValueFromAmount(nNet - nFee));
if (wtx.IsFromMe(filter))
- entry.push_back(Pair("fee", ValueFromAmount(nFee)));
+ entry.pushKV("fee", ValueFromAmount(nFee));
WalletTxToJSON(wtx, entry);
UniValue details(UniValue::VARR);
ListTransactions(pwallet, wtx, "*", 0, false, details, filter);
- entry.push_back(Pair("details", details));
+ entry.pushKV("details", details);
std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags());
- entry.push_back(Pair("hex", strHex));
+ entry.pushKV("hex", strHex);
return entry;
}
@@ -2135,14 +2184,14 @@ UniValue abandontransaction(const JSONRPCRequest& request)
return NullUniValue;
}
- if (request.fHelp || request.params.size() != 1)
+ if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"abandontransaction \"txid\"\n"
"\nMark in-wallet transaction <txid> as abandoned\n"
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
"It only works on transactions which are not included in a block and are not currently in the mempool.\n"
- "It has no effect on transactions which are already conflicted or abandoned.\n"
+ "It has no effect on transactions which are already abandoned.\n"
"\nArguments:\n"
"1. \"txid\" (string, required) The transaction id\n"
"\nResult:\n"
@@ -2150,6 +2199,7 @@ UniValue abandontransaction(const JSONRPCRequest& request)
+ HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
);
+ }
ObserveSafeMode();
@@ -2267,7 +2317,8 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
"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"
@@ -2296,6 +2347,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)) {
@@ -2309,7 +2371,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);
@@ -2641,8 +2702,8 @@ UniValue listlockunspent(const JSONRPCRequest& request)
for (COutPoint &outpt : vOutpts) {
UniValue o(UniValue::VOBJ);
- o.push_back(Pair("txid", outpt.hash.GetHex()));
- o.push_back(Pair("vout", (int)outpt.n));
+ o.pushKV("txid", outpt.hash.GetHex());
+ o.pushKV("vout", (int)outpt.n);
ret.push_back(o);
}
@@ -2702,7 +2763,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", "")
@@ -2720,24 +2781,24 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
- obj.push_back(Pair("walletname", pwallet->GetName()));
- obj.push_back(Pair("walletversion", pwallet->GetVersion()));
- obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance())));
- obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())));
- obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())));
- obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size()));
- obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime()));
- obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize));
+ obj.pushKV("walletname", pwallet->GetName());
+ obj.pushKV("walletversion", pwallet->GetVersion());
+ obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance()));
+ obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()));
+ obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()));
+ obj.pushKV("txcount", (int)pwallet->mapWallet.size());
+ obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
+ obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
- obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)));
+ obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
}
if (pwallet->IsCrypted()) {
- obj.push_back(Pair("unlocked_until", pwallet->nRelockTime));
+ obj.pushKV("unlocked_until", pwallet->nRelockTime);
}
- obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
+ obj.pushKV("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()));
if (!masterKeyID.IsNull())
- obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex()));
+ obj.pushKV("hdmasterkeyid", masterKeyID.GetHex());
return obj;
}
@@ -2942,31 +3003,31 @@ UniValue listunspent(const JSONRPCRequest& request)
continue;
UniValue entry(UniValue::VOBJ);
- entry.push_back(Pair("txid", out.tx->GetHash().GetHex()));
- entry.push_back(Pair("vout", out.i));
+ entry.pushKV("txid", out.tx->GetHash().GetHex());
+ entry.pushKV("vout", out.i);
if (fValidAddress) {
- entry.push_back(Pair("address", EncodeDestination(address)));
+ entry.pushKV("address", EncodeDestination(address));
if (pwallet->mapAddressBook.count(address)) {
- entry.push_back(Pair("account", pwallet->mapAddressBook[address].name));
+ entry.pushKV("account", pwallet->mapAddressBook[address].name);
}
if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID& hash = boost::get<CScriptID>(address);
CScript redeemScript;
if (pwallet->GetCScript(hash, redeemScript)) {
- entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())));
+ entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
}
}
}
- entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())));
- entry.push_back(Pair("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)));
- entry.push_back(Pair("confirmations", out.nDepth));
- entry.push_back(Pair("spendable", out.fSpendable));
- entry.push_back(Pair("solvable", out.fSolvable));
- entry.push_back(Pair("safe", out.fSafe));
+ entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
+ entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
+ entry.pushKV("confirmations", out.nDepth);
+ entry.pushKV("spendable", out.fSpendable);
+ entry.pushKV("solvable", out.fSolvable);
+ entry.pushKV("safe", out.fSafe);
results.push_back(entry);
}
@@ -2980,9 +3041,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"
@@ -2999,6 +3060,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"
@@ -3017,6 +3079,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"
@@ -3053,7 +3118,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];
@@ -3061,9 +3126,9 @@ 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.
{"feeRate", UniValueType()}, // will be checked below
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
{"replaceable", UniValueType(UniValue::VBOOL)},
@@ -3085,6 +3150,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();
@@ -3122,8 +3197,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");
@@ -3150,9 +3228,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
}
UniValue result(UniValue::VOBJ);
- result.push_back(Pair("hex", EncodeHexTx(tx)));
- result.push_back(Pair("changepos", changePosition));
- result.push_back(Pair("fee", ValueFromAmount(nFeeOut)));
+ result.pushKV("hex", EncodeHexTx(tx));
+ result.pushKV("changepos", changePosition);
+ result.pushKV("fee", ValueFromAmount(nFeeOut));
return result;
}
@@ -3173,8 +3251,8 @@ UniValue bumpfee(const JSONRPCRequest& request)
"If the change output is not big enough to cover the increased fee, the command will currently fail\n"
"instead of adding new inputs to compensate. (A future implementation could improve this.)\n"
"The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n"
- "By default, the new fee will be calculated automatically using estimatefee.\n"
- "The user can specify a confirmation target for estimatefee.\n"
+ "By default, the new fee will be calculated automatically using estimatesmartfee.\n"
+ "The user can specify a confirmation target for estimatesmartfee.\n"
"Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n"
"At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n"
"returned by getnetworkinfo) to enter the node's mempool.\n"
@@ -3294,14 +3372,14 @@ UniValue bumpfee(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
}
UniValue result(UniValue::VOBJ);
- result.push_back(Pair("txid", txid.GetHex()));
- result.push_back(Pair("origfee", ValueFromAmount(old_fee)));
- result.push_back(Pair("fee", ValueFromAmount(new_fee)));
+ result.pushKV("txid", txid.GetHex());
+ result.pushKV("origfee", ValueFromAmount(old_fee));
+ result.pushKV("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));
+ result.pushKV("errors", result_errors);
return result;
}
@@ -3376,30 +3454,41 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
);
}
- LOCK2(cs_main, pwallet->cs_wallet);
+ WalletRescanReserver reserver(pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
- CBlockIndex *pindexStart = chainActive.Genesis();
+ CBlockIndex *pindexStart = nullptr;
CBlockIndex *pindexStop = nullptr;
- if (!request.params[0].isNull()) {
- pindexStart = chainActive[request.params[0].get_int()];
- if (!pindexStart) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
- }
- }
+ CBlockIndex *pChainTip = nullptr;
+ {
+ LOCK(cs_main);
+ pindexStart = chainActive.Genesis();
+ pChainTip = chainActive.Tip();
- if (!request.params[1].isNull()) {
- pindexStop = chainActive[request.params[1].get_int()];
- if (!pindexStop) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
+ if (!request.params[0].isNull()) {
+ pindexStart = chainActive[request.params[0].get_int()];
+ if (!pindexStart) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
+ }
}
- else if (pindexStop->nHeight < pindexStart->nHeight) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater then 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) {
- CBlockIndex *block = pindexStop ? pindexStop : chainActive.Tip();
+ 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.");
@@ -3408,18 +3497,17 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
}
}
- CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(pindexStart, pindexStop, true);
+ 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 : chainActive.Tip();
+ 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);
@@ -3441,12 +3529,12 @@ 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","p2sh"} },
+ { "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"} },
@@ -3456,8 +3544,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"} },
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 b95bb14335..cafd69d075 100644
--- a/src/wallet/test/accounting_tests.cpp
+++ b/src/wallet/test/accounting_tests.cpp
@@ -1,4 +1,4 @@
-// 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.
diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp
index 3ff8c6d224..89b2c4e796 100644
--- a/src/wallet/test/crypto_tests.cpp
+++ b/src/wallet/test/crypto_tests.cpp
@@ -1,4 +1,4 @@
-// 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.
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index 3ee83d2d7c..7797f85f07 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -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.
@@ -13,6 +13,8 @@ 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 = MakeUnique<CWallet>(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index 292d654438..c03aec7f87 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.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/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 80e31a1ce0..9db5d63922 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -1,4 +1,4 @@
-// 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.
@@ -103,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);
@@ -370,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();
@@ -379,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, nullptr));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(nullBlock, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN);
}
@@ -397,7 +399,9 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
{
CWallet wallet;
AddKey(wallet, coinbaseKey);
- BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr));
+ WalletRescanReserver reserver(&wallet);
+ reserver.reserve();
+ BOOST_CHECK_EQUAL(oldTip, wallet.ScanForWalletTransactions(oldTip, nullptr, reserver));
BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN);
}
@@ -447,7 +451,8 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
- LOCK(cs_main);
+ g_address_type = OUTPUT_TYPE_DEFAULT;
+ g_change_type = OUTPUT_TYPE_DEFAULT;
// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
@@ -462,6 +467,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;
@@ -602,11 +609,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(), nullptr);
+ WalletRescanReserver reserver(wallet.get());
+ reserver.reserve();
+ wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver);
}
~ListCoinsTestingSetup()
@@ -627,10 +638,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;
}
@@ -641,7 +657,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.
@@ -664,17 +679,24 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2);
// Lock both coins. Confirm number of available coins drops to 0.
- std::vector<COutput> available;
- wallet->AvailableCoins(available);
- BOOST_CHECK_EQUAL(available.size(), 2);
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ 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));
}
}
- wallet->AvailableCoins(available);
- BOOST_CHECK_EQUAL(available.size(), 0);
-
+ {
+ LOCK2(cs_main, wallet->cs_wallet);
+ std::vector<COutput> available;
+ wallet->AvailableCoins(available);
+ BOOST_CHECK_EQUAL(available.size(), 0);
+ }
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
list = wallet->ListCoins();
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 080bfffd5f..a57c472126 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5 +1,5 @@
// 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.
@@ -23,12 +23,10 @@
#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>
@@ -36,7 +34,6 @@
#include <future>
#include <boost/algorithm/string/replace.hpp>
-#include <boost/thread.hpp>
std::vector<CWalletRef> vpwallets;
/** Transaction fee set by the user */
@@ -44,6 +41,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;
@@ -284,7 +283,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);
@@ -292,6 +291,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);
@@ -340,7 +347,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);
@@ -348,7 +355,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);
}
@@ -523,14 +530,11 @@ 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;
}
}
@@ -815,7 +819,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);
@@ -826,8 +830,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)
@@ -844,12 +848,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;
}
@@ -969,7 +975,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
if (!strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
- boost::thread t(runCommand, strCmd); // thread runs free
+ std::thread t(runCommand, strCmd);
+ t.detach(); // thread runs free
}
return true;
@@ -978,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);
@@ -1073,7 +1078,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK2(cs_main, cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
- return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool();
+ return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
}
bool CWallet::AbandonTransaction(const uint256& hashTx)
@@ -1089,7 +1094,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
CWalletTx& origtx = it->second;
- if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
+ if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -1286,12 +1291,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// ...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).
-
- std::promise<void> promise;
- CallFunctionInValidationInterfaceQueue([&promise] {
- promise.set_value();
- });
- promise.get_future().wait();
+ SyncWithValidationInterfaceQueue();
}
@@ -1607,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, nullptr, update);
+ const CBlockIndex* const failedBlock = ScanForWalletTransactions(startBlock, nullptr, reserver, update);
if (failedBlock) {
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
@@ -1638,12 +1639,17 @@ int64_t CWallet::RescanFromTime(int64_t startTime, bool update)
*
* 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, CBlockIndex* pindexStop, 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);
}
@@ -1651,24 +1657,42 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
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);
}
@@ -1678,14 +1702,20 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, CBlock
if (pindex == pindexStop) {
break;
}
- pindex = chainActive.Next(pindex);
+ {
+ 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;
}
@@ -1713,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);
}
@@ -1865,7 +1892,7 @@ 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())
{
@@ -1879,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;
@@ -2169,113 +2196,111 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
return balance;
}
-void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t &nMaximumCount, const int &nMinDepth, const int &nMaxDepth) const
+void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const
{
+ AssertLockHeld(cs_main);
+ AssertLockHeld(cs_wallet);
+
vCoins.clear();
+ CAmount nTotal = 0;
+ for (const auto& entry : mapWallet)
{
- LOCK2(cs_main, cs_wallet);
+ const uint256& wtxid = entry.first;
+ const CWalletTx* pcoin = &entry.second;
- CAmount nTotal = 0;
+ if (!CheckFinalTx(*pcoin->tx))
+ continue;
- for (const auto& entry : mapWallet)
- {
- const uint256& wtxid = entry.first;
- const CWalletTx* pcoin = &entry.second;
+ if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
+ continue;
- if (!CheckFinalTx(*pcoin->tx))
- continue;
+ int nDepth = pcoin->GetDepthInMainChain();
+ if (nDepth < 0)
+ continue;
- if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
- continue;
+ // We should not consider coins which aren't at least in our mempool
+ // It's possible for these to be conflicted via ancestors which we may never be able to detect
+ if (nDepth == 0 && !pcoin->InMempool())
+ continue;
- int nDepth = pcoin->GetDepthInMainChain();
- if (nDepth < 0)
- continue;
+ bool safeTx = pcoin->IsTrusted();
+
+ // We should not consider coins from transactions that are replacing
+ // other transactions.
+ //
+ // Example: There is a transaction A which is replaced by bumpfee
+ // transaction B. In this case, we want to prevent creation of
+ // a transaction B' which spends an output of B.
+ //
+ // Reason: If transaction A were initially confirmed, transactions B
+ // and B' would no longer be valid, so the user would have to create
+ // a new transaction C to replace B'. However, in the case of a
+ // one-block reorg, transactions B' and C might BOTH be accepted,
+ // when the user only wanted one of them. Specifically, there could
+ // be a 1-block reorg away from the chain where transactions A and C
+ // were accepted to another chain where B, B', and C were all
+ // accepted.
+ if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
+ safeTx = false;
+ }
- // We should not consider coins which aren't at least in our mempool
- // It's possible for these to be conflicted via ancestors which we may never be able to detect
- if (nDepth == 0 && !pcoin->InMempool())
- continue;
+ // Similarly, we should not consider coins from transactions that
+ // have been replaced. In the example above, we would want to prevent
+ // creation of a transaction A' spending an output of A, because if
+ // transaction B were initially confirmed, conflicting with A and
+ // A', we wouldn't want to the user to create a transaction D
+ // intending to replace A', but potentially resulting in a scenario
+ // where A, A', and D could all be accepted (instead of just B and
+ // D, or just A and A' like the user would want).
+ if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
+ safeTx = false;
+ }
- bool safeTx = pcoin->IsTrusted();
-
- // We should not consider coins from transactions that are replacing
- // other transactions.
- //
- // Example: There is a transaction A which is replaced by bumpfee
- // transaction B. In this case, we want to prevent creation of
- // a transaction B' which spends an output of B.
- //
- // Reason: If transaction A were initially confirmed, transactions B
- // and B' would no longer be valid, so the user would have to create
- // a new transaction C to replace B'. However, in the case of a
- // one-block reorg, transactions B' and C might BOTH be accepted,
- // when the user only wanted one of them. Specifically, there could
- // be a 1-block reorg away from the chain where transactions A and C
- // were accepted to another chain where B, B', and C were all
- // accepted.
- if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) {
- safeTx = false;
- }
+ if (fOnlySafe && !safeTx) {
+ continue;
+ }
- // Similarly, we should not consider coins from transactions that
- // have been replaced. In the example above, we would want to prevent
- // creation of a transaction A' spending an output of A, because if
- // transaction B were initially confirmed, conflicting with A and
- // A', we wouldn't want to the user to create a transaction D
- // intending to replace A', but potentially resulting in a scenario
- // where A, A', and D could all be accepted (instead of just B and
- // D, or just A and A' like the user would want).
- if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
- safeTx = false;
- }
+ if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ continue;
- if (fOnlySafe && !safeTx) {
+ for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
+ if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
continue;
- }
- if (nDepth < nMinDepth || nDepth > nMaxDepth)
+ if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue;
- for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
- if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount)
- continue;
-
- if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
- continue;
-
- if (IsLockedCoin(entry.first, i))
- continue;
+ if (IsLockedCoin(entry.first, i))
+ continue;
- if (IsSpent(wtxid, i))
- continue;
+ if (IsSpent(wtxid, i))
+ continue;
- isminetype mine = IsMine(pcoin->tx->vout[i]);
+ isminetype mine = IsMine(pcoin->tx->vout[i]);
- if (mine == ISMINE_NO) {
- continue;
- }
-
- bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
- bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
+ if (mine == ISMINE_NO) {
+ continue;
+ }
- vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
+ bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (coinControl && coinControl->fAllowWatchOnly && (mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
+ bool fSolvableIn = (mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) != ISMINE_NO;
- // Checks the sum amount of all UTXO's.
- if (nMinimumSumAmount != MAX_MONEY) {
- nTotal += pcoin->tx->vout[i].nValue;
+ vCoins.push_back(COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
- if (nTotal >= nMinimumSumAmount) {
- return;
- }
- }
+ // Checks the sum amount of all UTXO's.
+ if (nMinimumSumAmount != MAX_MONEY) {
+ nTotal += pcoin->tx->vout[i].nValue;
- // Checks the maximum number of UTXO's.
- if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ if (nTotal >= nMinimumSumAmount) {
return;
}
}
+
+ // Checks the maximum number of UTXO's.
+ if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
+ return;
+ }
}
}
}
@@ -2293,11 +2318,11 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
// avoid adding some extra complexity to the Qt code.
std::map<CTxDestination, std::vector<COutput>> result;
-
std::vector<COutput> availableCoins;
- AvailableCoins(availableCoins);
LOCK2(cs_main, cs_wallet);
+ AvailableCoins(availableCoins);
+
for (auto& coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
@@ -2592,9 +2617,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);
@@ -2602,8 +2626,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;
@@ -2613,34 +2642,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)
{
@@ -2736,7 +2790,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);
@@ -3625,6 +3682,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);
@@ -3996,7 +4054,14 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
}
nStart = GetTimeMillis();
- walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, 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();
@@ -4124,3 +4189,106 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
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 1bd0be7bd0..fefe415bb1 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,5 +1,5 @@
// 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.
@@ -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
@@ -395,7 +408,7 @@ public:
mapValue["timesmart"] = strprintf("%u", nTimeSmart);
}
- READWRITE(*(CMerkleTx*)this);
+ READWRITE(*static_cast<CMerkleTx*>(this));
std::vector<CMerkleTx> vUnused; //!< Used to be vtxPrev
READWRITE(vUnused);
READWRITE(mapValue);
@@ -445,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,
@@ -646,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.
@@ -655,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
@@ -761,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;
@@ -828,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.
@@ -874,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);
@@ -920,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);
@@ -929,8 +949,8 @@ 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, CBlockIndex* pindexStop, 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;
@@ -945,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();
@@ -1126,6 +1148,26 @@ public:
* 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. */
@@ -1215,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 5116d6419e..dd6835a06f 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,5 +1,5 @@
// 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.
@@ -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;
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index e815bcfeda..3691cfcb57 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,5 +1,5 @@
// 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.
diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp
index fbb5215a51..7c97b668ae 100644
--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -2,14 +2,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include "wallet/walletutil.h"
+#include <wallet/walletutil.h>
fs::path GetWalletDir()
{
fs::path path;
if (gArgs.IsArgSet("-walletdir")) {
- path = fs::system_complete(gArgs.GetArg("-walletdir", ""));
+ path = gArgs.GetArg("-walletdir", "");
if (!fs::is_directory(path)) {
// If the path specified doesn't exist, we return the deliberately
// invalid empty string.
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index a94f286a44..50ff736402 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -5,7 +5,8 @@
#ifndef BITCOIN_WALLET_UTIL_H
#define BITCOIN_WALLET_UTIL_H
-#include "util.h"
+#include <chainparamsbase.h>
+#include <util.h>
//! Get the path of the wallet directory.
fs::path GetWalletDir();