aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/coincontrol.cpp2
-rw-r--r--src/wallet/coincontrol.h17
-rw-r--r--src/wallet/coinselection.cpp7
-rw-r--r--src/wallet/crypter.cpp2
-rw-r--r--src/wallet/crypter.h2
-rw-r--r--src/wallet/db.cpp36
-rw-r--r--src/wallet/feebumper.cpp29
-rw-r--r--src/wallet/init.cpp15
-rw-r--r--src/wallet/ismine.cpp192
-rw-r--r--src/wallet/ismine.h5
-rw-r--r--src/wallet/psbtwallet.cpp30
-rw-r--r--src/wallet/psbtwallet.h4
-rw-r--r--src/wallet/rpcdump.cpp111
-rw-r--r--src/wallet/rpcwallet.cpp414
-rw-r--r--src/wallet/rpcwallet.h8
-rw-r--r--src/wallet/scriptpubkeyman.cpp1428
-rw-r--r--src/wallet/scriptpubkeyman.h466
-rw-r--r--src/wallet/test/coinselector_tests.cpp65
-rw-r--r--src/wallet/test/db_tests.cpp2
-rw-r--r--src/wallet/test/init_test_fixture.cpp2
-rw-r--r--src/wallet/test/init_test_fixture.h6
-rw-r--r--src/wallet/test/init_tests.cpp30
-rw-r--r--src/wallet/test/ismine_tests.cpp234
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp26
-rw-r--r--src/wallet/test/wallet_crypto_tests.cpp2
-rw-r--r--src/wallet/test/wallet_test_fixture.h6
-rw-r--r--src/wallet/test/wallet_tests.cpp120
-rw-r--r--src/wallet/wallet.cpp1772
-rw-r--r--src/wallet/wallet.h530
-rw-r--r--src/wallet/walletdb.cpp37
-rw-r--r--src/wallet/walletdb.h4
-rw-r--r--src/wallet/wallettool.cpp26
-rw-r--r--src/wallet/wallettool.h2
-rw-r--r--src/wallet/walletutil.h50
34 files changed, 3379 insertions, 2303 deletions
diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp
index 14513bc9e9..c83e598825 100644
--- a/src/wallet/coincontrol.cpp
+++ b/src/wallet/coincontrol.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2018 The Bitcoin Core developers
+// Copyright (c) 2018-2019 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/coincontrol.h b/src/wallet/coincontrol.h
index 92a290530c..2893d0ab3d 100644
--- a/src/wallet/coincontrol.h
+++ b/src/wallet/coincontrol.h
@@ -5,16 +5,19 @@
#ifndef BITCOIN_WALLET_COINCONTROL_H
#define BITCOIN_WALLET_COINCONTROL_H
+#include <optional.h>
+#include <outputtype.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <primitives/transaction.h>
-#include <wallet/wallet.h>
-
-#include <boost/optional.hpp>
+#include <script/standard.h>
const int DEFAULT_MIN_DEPTH = 0;
const int DEFAULT_MAX_DEPTH = 9999999;
+//! Default for -avoidpartialspends
+static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false;
+
/** Coin Control Features. */
class CCoinControl
{
@@ -22,7 +25,7 @@ public:
//! Custom change destination, if not set an address is generated
CTxDestination destChange;
//! Override the default change type if set, ignored if destChange is set
- boost::optional<OutputType> m_change_type;
+ Optional<OutputType> m_change_type;
//! If false, allows unselected inputs, but requires all selected inputs be used
bool fAllowOtherInputs;
//! Includes watch only addresses which are solvable
@@ -30,11 +33,11 @@ public:
//! Override automatic min/max checks on fee, m_feerate must be set if true
bool fOverrideFeeRate;
//! Override the wallet's m_pay_tx_fee if set
- boost::optional<CFeeRate> m_feerate;
+ Optional<CFeeRate> m_feerate;
//! Override the default confirmation target if set
- boost::optional<unsigned int> m_confirm_target;
+ Optional<unsigned int> m_confirm_target;
//! Override the wallet's m_signal_rbf if set
- boost::optional<bool> m_signal_bip125_rbf;
+ Optional<bool> m_signal_bip125_rbf;
//! Avoid partial use of funds sent to a given address
bool m_avoid_partial_spends;
//! Forbids inclusion of dirty (previously used) addresses
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 8a37f374a1..5bbb2c0ad0 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -1,14 +1,13 @@
-// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Copyright (c) 2017-2019 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/coinselection.h>
+#include <optional.h>
#include <util/system.h>
#include <util/moneystr.h>
-#include <boost/optional.hpp>
-
// Descending order comparator
struct {
bool operator()(const OutputGroup& a, const OutputGroup& b) const
@@ -219,7 +218,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
nValueRet = 0;
// List of values less than target
- boost::optional<OutputGroup> lowest_larger;
+ Optional<OutputGroup> lowest_larger;
std::vector<OutputGroup> applicable_groups;
CAmount nTotalLower = 0;
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index f6179aa298..b50f00e7d1 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -23,7 +23,7 @@ int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, cons
unsigned char buf[CSHA512::OUTPUT_SIZE];
CSHA512 di;
- di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size());
+ di.Write((const unsigned char*)strKeyData.data(), strKeyData.size());
di.Write(chSalt.data(), chSalt.size());
di.Finalize(buf);
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index 4367a5047f..f59c63260e 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 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 e48eee6c2c..8b042162d8 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -44,7 +44,7 @@ void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filena
}
}
-CCriticalSection cs_db;
+RecursiveMutex cs_db;
std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment.
} // namespace
@@ -166,10 +166,9 @@ BerkeleyEnvironment::~BerkeleyEnvironment()
bool BerkeleyEnvironment::Open(bool retry)
{
- if (fDbEnvInit)
+ if (fDbEnvInit) {
return true;
-
- boost::this_thread::interruption_point();
+ }
fs::path pathIn = strPath;
TryCreateDirectories(pathIn);
@@ -238,14 +237,12 @@ bool BerkeleyEnvironment::Open(bool retry)
return true;
}
-//! Construct an in-memory mock Berkeley environment for testing and as a place-holder for g_dbenvs emplace
+//! Construct an in-memory mock Berkeley environment for testing
BerkeleyEnvironment::BerkeleyEnvironment()
{
Reset();
- boost::this_thread::interruption_point();
-
- LogPrint(BCLog::DB, "BerkeleyEnvironment::MakeMock\n");
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
@@ -263,8 +260,9 @@ BerkeleyEnvironment::BerkeleyEnvironment()
DB_THREAD |
DB_PRIVATE,
S_IRUSR | S_IWUSR);
- if (ret > 0)
+ if (ret > 0) {
throw std::runtime_error(strprintf("BerkeleyEnvironment::MakeMock: Error %d opening database environment.", ret));
+ }
fDbEnvInit = true;
fMockDb = true;
@@ -652,7 +650,7 @@ void BerkeleyEnvironment::ReloadDbEnv()
{
// Make sure that no Db's are in use
AssertLockNotHeld(cs_db);
- std::unique_lock<CCriticalSection> lock(cs_db);
+ std::unique_lock<RecursiveMutex> lock(cs_db);
m_db_in_use.wait(lock, [this](){
for (auto& count : mapFileUseCount) {
if (count.second > 0) return false;
@@ -767,7 +765,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
{
int64_t nStart = GetTimeMillis();
// Flush log data to the actual data file on all files that are not in use
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit)
return;
{
@@ -776,21 +774,21 @@ void BerkeleyEnvironment::Flush(bool fShutdown)
while (mi != mapFileUseCount.end()) {
std::string strFile = (*mi).first;
int nRefCount = (*mi).second;
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
if (nRefCount == 0) {
// Move log data to the dat file
CloseDb(strFile);
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s detach\n", strFile);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile);
if (!fMockDb)
dbenv->lsn_reset(strFile.c_str(), 0);
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile);
mapFileUseCount.erase(mi++);
} else
mi++;
}
- LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
+ LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
if (fShutdown) {
char** listp;
if (mapFileUseCount.empty()) {
@@ -830,7 +828,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
std::map<std::string, int>::iterator mi = env->mapFileUseCount.find(strFile);
if (mi != env->mapFileUseCount.end())
{
- LogPrint(BCLog::DB, "Flushing %s\n", strFile);
+ LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile);
int64_t nStart = GetTimeMillis();
// Flush wallet file so it's self contained
@@ -838,7 +836,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database)
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(mi++);
- LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
+ LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
ret = true;
}
}
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 0a4bb3f396..b93b9ef1bc 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -16,7 +16,7 @@
//! Check whether transaction has descendant in wallet or mempool, or has been
//! mined, or conflicts with a mined transaction. Return a feebumper::Result.
-static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
if (wallet.HasWalletSpend(wtx.GetHash())) {
errors.push_back("Transaction has descendants in the wallet");
@@ -30,7 +30,7 @@ static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chai
}
}
- if (wtx.GetDepthInMainChain(locked_chain) != 0) {
+ if (wtx.GetDepthInMainChain() != 0) {
errors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
return feebumper::Result::WALLET_ERROR;
}
@@ -47,7 +47,8 @@ static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chai
// 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)) {
+ isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
+ if (!wallet.IsAllFromMe(*wtx.tx, filter)) {
errors.push_back("Transaction contains inputs that don't belong to this wallet");
return feebumper::Result::WALLET_ERROR;
}
@@ -78,7 +79,8 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
CFeeRate incrementalRelayFee = std::max(wallet.chain().relayIncrementalFee(), CFeeRate(WALLET_INCREMENTAL_RELAY_FEE));
// Given old total fee and transaction size, calculate the old feeRate
- CAmount old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
+ CAmount old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
CFeeRate nOldFeeRate(old_fee, txSize);
// Min total fee is old fee + relay fee
@@ -108,12 +110,11 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
return feebumper::Result::OK;
}
-static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, CCoinControl& coin_control, CAmount& old_fee)
+static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, CCoinControl& coin_control)
{
// Get the fee rate of the original transaction. This is calculated from
// the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the
// result.
- old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
CFeeRate feerate(old_fee, txSize);
feerate += CFeeRate(1);
@@ -146,7 +147,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid)
if (wtx == nullptr) return false;
std::vector<std::string> errors_dummy;
- feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy);
+ feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy);
return res == feebumper::Result::OK;
}
@@ -165,7 +166,7 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co
}
const CWalletTx& wtx = it->second;
- Result result = PreconditionChecks(*locked_chain, *wallet, wtx, errors);
+ Result result = PreconditionChecks(*wallet, wtx, errors);
if (result != Result::OK) {
return result;
}
@@ -196,7 +197,8 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co
}
// calculate the old fee and fee-rate
- old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ isminefilter filter = wallet->GetLegacyScriptPubKeyMan() && wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
+ old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
CFeeRate nOldFeeRate(old_fee, txSize);
// The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
// future proof against changes to network wide policy for incremental relay
@@ -291,7 +293,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
}
const CWalletTx& wtx = it->second;
- Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors);
+ Result result = PreconditionChecks(wallet, wtx, errors);
if (result != Result::OK) {
return result;
}
@@ -309,6 +311,9 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
}
}
+ isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
+ old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
+
if (coin_control.m_feerate) {
// The user provided a feeRate argument.
// We calculate this here to avoid compiler warning on the cs_wallet lock
@@ -319,7 +324,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
}
} else {
// The user did not provide a feeRate argument
- new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, new_coin_control, old_fee);
+ new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, old_fee, new_coin_control);
}
// Fill in required inputs we are double-spending(all of them)
@@ -382,7 +387,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti
CWalletTx& oldWtx = it->second;
// make sure the transaction still has no descendants and hasn't been mined in the meantime
- Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors);
+ Result result = PreconditionChecks(wallet, oldWtx, errors);
if (result != Result::OK) {
return result;
}
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 3657a157b6..2ebc9aba39 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -6,10 +6,13 @@
#include <init.h>
#include <interfaces/chain.h>
#include <net.h>
+#include <node/context.h>
#include <outputtype.h>
+#include <ui_interface.h>
#include <util/moneystr.h>
#include <util/system.h>
#include <util/translation.h>
+#include <wallet/coincontrol.h>
#include <wallet/wallet.h>
#include <walletinitinterface.h>
@@ -25,8 +28,8 @@ public:
//! Wallets parameter interaction
bool ParameterInteraction() const override;
- //! Add wallets that should be opened to list of init interfaces.
- void Construct(InitInterfaces& interfaces) const override;
+ //! Add wallets that should be opened to list of chain clients.
+ void Construct(NodeContext& node) const override;
};
const WalletInitInterface& g_wallet_init_interface = WalletInit();
@@ -43,7 +46,7 @@ void WalletInit::AddWalletOptions() const
gArgs.AddArg("-fallbackfee=<amt>", strprintf("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data. 0 to entirely disable the fallbackfee feature. (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u)", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+ gArgs.AddArg("-keypool=<n>", strprintf("Set key pool size to <n> (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
gArgs.AddArg("-maxtxfee=<amt>", strprintf("Maximum total fees (in %s) to use in a single wallet transaction; setting this too low may abort large transactions (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-mintxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for transaction creation (default: %s)",
@@ -57,7 +60,7 @@ void WalletInit::AddWalletOptions() const
gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
- gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+ gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
#if HAVE_SYSTEM
gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
#endif
@@ -125,12 +128,12 @@ bool WalletInit::ParameterInteraction() const
return true;
}
-void WalletInit::Construct(InitInterfaces& interfaces) const
+void WalletInit::Construct(NodeContext& node) const
{
if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
LogPrintf("Wallet disabled!\n");
return;
}
gArgs.SoftSetArg("-wallet", "");
- interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet")));
+ node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, gArgs.GetArgs("-wallet")));
}
diff --git a/src/wallet/ismine.cpp b/src/wallet/ismine.cpp
deleted file mode 100644
index 029b922785..0000000000
--- a/src/wallet/ismine.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2018 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/ismine.h>
-
-#include <key.h>
-#include <script/script.h>
-#include <script/signingprovider.h>
-#include <wallet/wallet.h>
-
-typedef std::vector<unsigned char> valtype;
-
-namespace {
-
-/**
- * This is an enum that tracks the execution context of a script, similar to
- * SigVersion in script/interpreter. It is separate however because we want to
- * distinguish between top-level scriptPubKey execution and P2SH redeemScript
- * execution (a distinction that has no impact on consensus rules).
- */
-enum class IsMineSigVersion
-{
- TOP = 0, //!< scriptPubKey execution
- P2SH = 1, //!< P2SH redeemScript
- WITNESS_V0 = 2, //!< P2WSH witness script execution
-};
-
-/**
- * This is an internal representation of isminetype + invalidity.
- * Its order is significant, as we return the max of all explored
- * possibilities.
- */
-enum class IsMineResult
-{
- NO = 0, //!< Not ours
- WATCH_ONLY = 1, //!< Included in watch-only balance
- SPENDABLE = 2, //!< Included in all balances
- INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness)
-};
-
-bool PermitsUncompressed(IsMineSigVersion sigversion)
-{
- return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;
-}
-
-bool HaveKeys(const std::vector<valtype>& pubkeys, const CWallet& keystore)
-{
- for (const valtype& pubkey : pubkeys) {
- CKeyID keyID = CPubKey(pubkey).GetID();
- if (!keystore.HaveKey(keyID)) return false;
- }
- return true;
-}
-
-IsMineResult IsMineInner(const CWallet& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
-{
- IsMineResult ret = IsMineResult::NO;
-
- std::vector<valtype> vSolutions;
- txnouttype whichType = Solver(scriptPubKey, vSolutions);
-
- CKeyID keyID;
- switch (whichType)
- {
- case TX_NONSTANDARD:
- case TX_NULL_DATA:
- case TX_WITNESS_UNKNOWN:
- break;
- case TX_PUBKEY:
- keyID = CPubKey(vSolutions[0]).GetID();
- if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) {
- return IsMineResult::INVALID;
- }
- if (keystore.HaveKey(keyID)) {
- ret = std::max(ret, IsMineResult::SPENDABLE);
- }
- break;
- case TX_WITNESS_V0_KEYHASH:
- {
- if (sigversion == IsMineSigVersion::WITNESS_V0) {
- // P2WPKH inside P2WSH is invalid.
- return IsMineResult::INVALID;
- }
- if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
- // We do not support bare witness outputs unless the P2SH version of it would be
- // acceptable as well. This protects against matching before segwit activates.
- // This also applies to the P2WSH case.
- break;
- }
- ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
- break;
- }
- case TX_PUBKEYHASH:
- keyID = CKeyID(uint160(vSolutions[0]));
- if (!PermitsUncompressed(sigversion)) {
- CPubKey pubkey;
- if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) {
- return IsMineResult::INVALID;
- }
- }
- if (keystore.HaveKey(keyID)) {
- ret = std::max(ret, IsMineResult::SPENDABLE);
- }
- break;
- case TX_SCRIPTHASH:
- {
- if (sigversion != IsMineSigVersion::TOP) {
- // P2SH inside P2WSH or P2SH is invalid.
- return IsMineResult::INVALID;
- }
- CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
- CScript subscript;
- if (keystore.GetCScript(scriptID, subscript)) {
- ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
- }
- break;
- }
- case TX_WITNESS_V0_SCRIPTHASH:
- {
- if (sigversion == IsMineSigVersion::WITNESS_V0) {
- // P2WSH inside P2WSH is invalid.
- return IsMineResult::INVALID;
- }
- if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
- break;
- }
- uint160 hash;
- CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin());
- CScriptID scriptID = CScriptID(hash);
- CScript subscript;
- if (keystore.GetCScript(scriptID, subscript)) {
- ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
- }
- break;
- }
-
- case TX_MULTISIG:
- {
- // Never treat bare multisig outputs as ours (they can still be made watchonly-though)
- if (sigversion == IsMineSigVersion::TOP) {
- break;
- }
-
- // Only consider transactions "mine" if we own ALL the
- // keys involved. Multi-signature transactions that are
- // partially owned (somebody else has a key that can spend
- // them) enable spend-out-from-under-you attacks, especially
- // in shared-wallet situations.
- std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
- if (!PermitsUncompressed(sigversion)) {
- for (size_t i = 0; i < keys.size(); i++) {
- if (keys[i].size() != 33) {
- return IsMineResult::INVALID;
- }
- }
- }
- if (HaveKeys(keys, keystore)) {
- ret = std::max(ret, IsMineResult::SPENDABLE);
- }
- break;
- }
- }
-
- if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
- ret = std::max(ret, IsMineResult::WATCH_ONLY);
- }
- return ret;
-}
-
-} // namespace
-
-isminetype IsMine(const CWallet& keystore, const CScript& scriptPubKey)
-{
- switch (IsMineInner(keystore, scriptPubKey, IsMineSigVersion::TOP)) {
- case IsMineResult::INVALID:
- case IsMineResult::NO:
- return ISMINE_NO;
- case IsMineResult::WATCH_ONLY:
- return ISMINE_WATCH_ONLY;
- case IsMineResult::SPENDABLE:
- return ISMINE_SPENDABLE;
- }
- assert(false);
-}
-
-isminetype IsMine(const CWallet& keystore, const CTxDestination& dest)
-{
- CScript script = GetScriptForDestination(dest);
- return IsMine(keystore, script);
-}
diff --git a/src/wallet/ismine.h b/src/wallet/ismine.h
index 41555fcb93..5cdd7dff80 100644
--- a/src/wallet/ismine.h
+++ b/src/wallet/ismine.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -28,9 +28,6 @@ enum isminetype : unsigned int
/** used for bitflags of isminetype */
typedef uint8_t isminefilter;
-isminetype IsMine(const CWallet& wallet, const CScript& scriptPubKey);
-isminetype IsMine(const CWallet& wallet, const CTxDestination& dest);
-
/**
* Cachable amount subdivided into watchonly and spendable parts.
*/
diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp
index 721a244afb..d995fb06d4 100644
--- a/src/wallet/psbtwallet.cpp
+++ b/src/wallet/psbtwallet.cpp
@@ -39,12 +39,38 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps
return TransactionError::SIGHASH_MISMATCH;
}
- complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type);
+ // Get the scriptPubKey to know which SigningProvider to use
+ CScript script;
+ if (!input.witness_utxo.IsNull()) {
+ script = input.witness_utxo.scriptPubKey;
+ } else if (input.non_witness_utxo) {
+ if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
+ return TransactionError::MISSING_INPUTS;
+ }
+ script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
+ } else {
+ // There's no UTXO so we can just skip this now
+ complete = false;
+ continue;
+ }
+ SignatureData sigdata;
+ input.FillSignatureData(sigdata);
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(script, sigdata);
+ if (!provider) {
+ complete = false;
+ continue;
+ }
+
+ complete &= SignPSBTInput(HidingSigningProvider(provider.get(), !sign, !bip32derivs), psbtx, i, sighash_type);
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
- UpdatePSBTOutput(HidingSigningProvider(pwallet, true, !bip32derivs), psbtx, i);
+ const CTxOut& out = psbtx.tx->vout.at(i);
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(out.scriptPubKey);
+ if (provider) {
+ UpdatePSBTOutput(HidingSigningProvider(provider.get(), true, !bip32derivs), psbtx, i);
+ }
}
return TransactionError::OK;
diff --git a/src/wallet/psbtwallet.h b/src/wallet/psbtwallet.h
index a7e52df6d9..b35a0a58d1 100644
--- a/src/wallet/psbtwallet.h
+++ b/src/wallet/psbtwallet.h
@@ -15,8 +15,8 @@
* finalize.) Sets `error` and returns false if something goes wrong.
*
* @param[in] pwallet pointer to a wallet
- * @param[in] &psbtx reference to PartiallySignedTransaction to fill in
- * @param[out] &complete indicates whether the PSBT is now complete
+ * @param[in] psbtx PartiallySignedTransaction to fill in
+ * @param[out] complete indicates whether the PSBT is now complete
* @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify)
* @param[in] sign whether to sign or not
* @param[in] bip32derivs whether to fill in bip32 derivation information if available
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 1cd4cb93b4..b730d4a4dd 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -23,23 +23,10 @@
#include <tuple>
#include <boost/algorithm/string.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
#include <univalue.h>
-int64_t static DecodeDumpTime(const std::string &str) {
- static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
- static const std::locale loc(std::locale::classic(),
- new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
- std::istringstream iss(str);
- iss.imbue(loc);
- boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
- iss >> ptime;
- if (ptime.is_not_a_date_time())
- return 0;
- return (ptime - epoch).total_seconds();
-}
std::string static EncodeDumpString(const std::string &str) {
std::stringstream ret;
@@ -67,11 +54,11 @@ static std::string DecodeDumpString(const std::string &str) {
return ret.str();
}
-static bool GetWalletAddressesForKey(CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
bool fLabelFound = false;
CKey key;
- pwallet->GetKey(keyid, key);
+ spk_man->GetKey(keyid, key);
for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) {
if (pwallet->mapAddressBook.count(dest)) {
if (!strAddr.empty()) {
@@ -138,6 +125,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
}
+ EnsureLegacyScriptPubKeyMan(*wallet, true);
+
WalletRescanReserver reserver(pwallet);
bool fRescan = true;
{
@@ -170,7 +159,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
+ CHECK_NONFATAL(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
{
pwallet->MarkDirty();
@@ -264,6 +253,7 @@ UniValue importaddress(const JSONRPCRequest& request)
},
}.Check(request);
+ EnsureLegacyScriptPubKeyMan(*pwallet, true);
std::string strLabel;
if (!request.params[1].isNull())
@@ -326,7 +316,7 @@ UniValue importaddress(const JSONRPCRequest& request)
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions(*locked_chain);
+ pwallet->ReacceptWalletTransactions();
}
}
@@ -364,28 +354,26 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
//Search partial merkle tree in proof for our transaction and index in valid block
std::vector<uint256> vMatch;
std::vector<unsigned int> vIndex;
- unsigned int txnIndex = 0;
- if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) {
-
- auto locked_chain = pwallet->chain().lock();
- if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
- }
-
- std::vector<uint256>::const_iterator it;
- if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
- }
+ if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
+ }
- txnIndex = vIndex[it - vMatch.begin()];
+ auto locked_chain = pwallet->chain().lock();
+ Optional<int> height = locked_chain->getBlockHeight(merkleBlock.header.GetHash());
+ if (height == nullopt) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
- else {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
+
+ std::vector<uint256>::const_iterator it;
+ if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
}
- wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
+ unsigned int txnIndex = vIndex[it - vMatch.begin()];
+
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *height, merkleBlock.header.GetHash(), txnIndex);
+ wtx.m_confirm = confirm;
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
if (pwallet->IsMine(*wtx.tx)) {
@@ -466,6 +454,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
},
}.Check(request);
+ EnsureLegacyScriptPubKeyMan(*wallet, true);
std::string strLabel;
if (!request.params[1].isNull())
@@ -516,7 +505,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions(*locked_chain);
+ pwallet->ReacceptWalletTransactions();
}
}
@@ -549,6 +538,8 @@ UniValue importwallet(const JSONRPCRequest& request)
},
}.Check(request);
+ EnsureLegacyScriptPubKeyMan(*wallet, true);
+
if (pwallet->chain().havePruned()) {
// Exit early and print an error.
// If a block is pruned after this check, we will import the key(s),
@@ -598,7 +589,7 @@ UniValue importwallet(const JSONRPCRequest& request)
continue;
CKey key = DecodeSecret(vstr[0]);
if (key.IsValid()) {
- int64_t nTime = DecodeDumpTime(vstr[1]);
+ int64_t nTime = ParseISO8601DateTime(vstr[1]);
std::string strLabel;
bool fLabel = true;
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
@@ -617,7 +608,7 @@ UniValue importwallet(const JSONRPCRequest& request)
} else if(IsHex(vstr[0])) {
std::vector<unsigned char> vData(ParseHex(vstr[0]));
CScript script = CScript(vData.begin(), vData.end());
- int64_t birth_time = DecodeDumpTime(vstr[1]);
+ int64_t birth_time = ParseISO8601DateTime(vstr[1]);
scripts.push_back(std::pair<CScript, int64_t>(script, birth_time));
}
}
@@ -637,7 +628,7 @@ UniValue importwallet(const JSONRPCRequest& request)
std::string label = std::get<3>(key_tuple);
CPubKey pubkey = key.GetPubKey();
- assert(key.VerifyPubKey(pubkey));
+ CHECK_NONFATAL(key.VerifyPubKey(pubkey));
CKeyID keyid = pubkey.GetID();
pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid)));
@@ -706,8 +697,10 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
},
}.Check(request);
+ LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet);
+
auto locked_chain = pwallet->chain().lock();
- LOCK(pwallet->cs_wallet);
+ LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
EnsureWalletIsUnlocked(pwallet);
@@ -716,12 +709,12 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
- auto keyid = GetKeyForDestination(*pwallet, dest);
+ auto keyid = GetKeyForDestination(spk_man, dest);
if (keyid.IsNull()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
}
CKey vchSecret;
- if (!pwallet->GetKey(keyid, vchSecret)) {
+ if (!spk_man.GetKey(keyid, vchSecret)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
}
return EncodeSecret(vchSecret);
@@ -755,8 +748,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
},
}.Check(request);
+ LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet);
+
auto locked_chain = pwallet->chain().lock();
- LOCK(pwallet->cs_wallet);
+ LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
EnsureWalletIsUnlocked(pwallet);
@@ -778,10 +773,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
std::map<CKeyID, int64_t> mapKeyBirth;
- const std::map<CKeyID, int64_t>& mapKeyPool = pwallet->GetAllReserveKeys();
+ const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys();
pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth);
- std::set<CScriptID> scripts = pwallet->GetCScripts();
+ std::set<CScriptID> scripts = spk_man.GetCScripts();
// sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
@@ -800,11 +795,11 @@ UniValue dumpwallet(const JSONRPCRequest& request)
file << "\n";
// add the base58check encoded extended master if the wallet uses HD
- CKeyID seed_id = pwallet->GetHDChain().seed_id;
+ CKeyID seed_id = spk_man.GetHDChain().seed_id;
if (!seed_id.IsNull())
{
CKey seed;
- if (pwallet->GetKey(seed_id, seed)) {
+ if (spk_man.GetKey(seed_id, seed)) {
CExtKey masterKey;
masterKey.SetSeed(seed.begin(), seed.size());
@@ -817,20 +812,20 @@ UniValue dumpwallet(const JSONRPCRequest& request)
std::string strAddr;
std::string strLabel;
CKey key;
- if (pwallet->GetKey(keyid, key)) {
+ if (spk_man.GetKey(keyid, key)) {
file << strprintf("%s %s ", EncodeSecret(key), strTime);
- if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) {
+ if (GetWalletAddressesForKey(&spk_man, pwallet, keyid, strAddr, strLabel)) {
file << strprintf("label=%s", strLabel);
} else if (keyid == seed_id) {
file << "hdseed=1";
} else if (mapKeyPool.count(keyid)) {
file << "reserve=1";
- } else if (pwallet->mapKeyMetadata[keyid].hdKeypath == "s") {
+ } else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") {
file << "inactivehdseed=1";
} else {
file << "change=1";
}
- file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : ""));
+ file << strprintf(" # addr=%s%s\n", strAddr, (spk_man.mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(spk_man.mapKeyMetadata[keyid].key_origin.path) : ""));
}
}
file << "\n";
@@ -839,11 +834,11 @@ UniValue dumpwallet(const JSONRPCRequest& request)
std::string create_time = "0";
std::string address = EncodeDestination(ScriptHash(scriptid));
// get birth times for scripts with metadata
- auto it = pwallet->m_script_metadata.find(scriptid);
- if (it != pwallet->m_script_metadata.end()) {
+ auto it = spk_man.m_script_metadata.find(scriptid);
+ if (it != spk_man.m_script_metadata.end()) {
create_time = FormatISO8601DateTime(it->second.nCreateTime);
}
- if(pwallet->GetCScript(scriptid, script)) {
+ if(spk_man.GetCScript(scriptid, script)) {
file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time);
file << strprintf(" # addr=%s\n", address);
}
@@ -899,7 +894,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
case TX_SCRIPTHASH: {
if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH");
if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH");
- assert(script_ctx == ScriptContext::TOP);
+ CHECK_NONFATAL(script_ctx == ScriptContext::TOP);
CScriptID id = CScriptID(uint160(solverdata[0]));
auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later.
if (!subscript) return "missing redeemscript";
@@ -1219,7 +1214,7 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
// Check whether we have any work to do
for (const CScript& script : script_pub_keys) {
- if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) {
+ if (pwallet->IsMine(script) & ISMINE_SPENDABLE) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")");
}
}
@@ -1290,7 +1285,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
/* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
},
- {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n"
+ {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n"
" or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
" key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
" \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
@@ -1339,6 +1334,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ});
+ EnsureLegacyScriptPubKeyMan(*wallet, true);
+
const UniValue& requests = mainRequest.params[0];
//Default options
@@ -1406,7 +1403,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
- pwallet->ReacceptWalletTransactions(*locked_chain);
+ pwallet->ReacceptWalletTransactions();
}
if (pwallet->IsAbortingRescan()) {
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index e571501221..ab0819a821 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,13 +1,13 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <amount.h>
#include <core_io.h>
-#include <init.h>
#include <interfaces/chain.h>
#include <key_io.h>
+#include <node/context.h>
#include <outputtype.h>
#include <policy/feerate.h>
#include <policy/fees.h>
@@ -68,7 +68,7 @@ static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWall
/** Checks if a CKey is in the given CWallet compressed or otherwise*/
-bool HaveKey(const CWallet& wallet, const CKey& key)
+bool HaveKey(const SigningProvider& wallet, const CKey& key)
{
CKey key2;
key2.Set(key.begin(), key.end(), !key.IsCompressed());
@@ -124,19 +124,33 @@ void EnsureWalletIsUnlocked(const CWallet* pwallet)
}
}
+// also_create should only be set to true only when the RPC is expected to add things to a blank wallet and make it no longer blank
+LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create)
+{
+ LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan();
+ if (!spk_man && also_create) {
+ spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
+ }
+ if (!spk_man) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
+ }
+ return *spk_man;
+}
+
static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry)
{
- int confirms = wtx.GetDepthInMainChain(locked_chain);
+ int confirms = wtx.GetDepthInMainChain();
entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase())
entry.pushKV("generated", true);
if (confirms > 0)
{
entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
+ entry.pushKV("blockheight", wtx.m_confirm.block_height);
entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time;
bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
- assert(found_block);
+ CHECK_NONFATAL(found_block);
entry.pushKV("blocktime", block_time);
} else {
entry.pushKV("trusted", wtx.IsTrusted(locked_chain));
@@ -303,7 +317,7 @@ static UniValue setlabel(const JSONRPCRequest& request)
std::string label = LabelFromValue(request.params[1]);
- if (IsMine(*pwallet, dest)) {
+ if (pwallet->IsMine(dest)) {
pwallet->SetAddressBook(dest, label, "receive");
} else {
pwallet->SetAddressBook(dest, label, "send");
@@ -550,9 +564,15 @@ static UniValue signmessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
+ CScript script_pub_key = GetScriptForDestination(*pkhash);
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(script_pub_key);
+ if (!provider) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
+ }
+
CKey key;
CKeyID keyID(*pkhash);
- if (!pwallet->GetKey(keyID, key)) {
+ if (!provider->GetKey(keyID, key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
@@ -610,7 +630,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
CScript scriptPubKey = GetScriptForDestination(dest);
- if (!IsMine(*pwallet, scriptPubKey)) {
+ if (!pwallet->IsMine(scriptPubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
}
@@ -629,7 +649,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request)
for (const CTxOut& txout : wtx.tx->vout)
if (txout.scriptPubKey == scriptPubKey)
- if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth)
+ if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue;
}
@@ -694,8 +714,8 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request)
for (const CTxOut& txout : wtx.tx->vout)
{
CTxDestination address;
- if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) {
- if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth)
+ if (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) {
+ if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue;
}
}
@@ -935,7 +955,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
}
RPCHelpMan{"addmultisigaddress",
- "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
+ "\nAdd an 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"
@@ -964,8 +984,10 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
},
}.Check(request);
+ LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
+
auto locked_chain = pwallet->chain().lock();
- LOCK(pwallet->cs_wallet);
+ LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
std::string label;
if (!request.params[2].isNull())
@@ -980,7 +1002,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
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()));
+ pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str()));
}
}
@@ -993,7 +1015,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
// Construct using pay-to-script-hash:
CScript inner;
- CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *pwallet, inner);
+ CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner);
pwallet->SetAddressBook(dest, label, "send");
UniValue result(UniValue::VOBJ);
@@ -1050,7 +1072,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co
continue;
}
- int nDepth = wtx.GetDepthInMainChain(locked_chain);
+ int nDepth = wtx.GetDepthInMainChain();
if (nDepth < nMinDepth)
continue;
@@ -1064,7 +1086,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co
continue;
}
- isminefilter mine = IsMine(*pwallet, address);
+ isminefilter mine = pwallet->IsMine(address);
if(!(mine & filter))
continue;
@@ -1288,7 +1310,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con
for (const COutputEntry& s : listSent)
{
UniValue entry(UniValue::VOBJ);
- if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) {
+ if (involvesWatchonly || (pwallet->IsMine(s.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, s.destination);
@@ -1307,8 +1329,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con
}
// Received
- if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth)
- {
+ if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) {
for (const COutputEntry& r : listReceived)
{
std::string label;
@@ -1319,15 +1340,15 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con
continue;
}
UniValue entry(UniValue::VOBJ);
- if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) {
+ if (involvesWatchonly || (pwallet->IsMine(r.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase())
{
- if (wtx.GetDepthInMainChain(locked_chain) < 1)
+ if (wtx.GetDepthInMainChain() < 1)
entry.pushKV("category", "orphan");
- else if (wtx.IsImmatureCoinBase(locked_chain))
+ else if (wtx.IsImmatureCoinBase())
entry.pushKV("category", "immature");
else
entry.pushKV("category", "generate");
@@ -1355,15 +1376,16 @@ static const std::string TransactionDescriptionString()
" \"generated\": xxx, (bool) Only present if transaction only input is a coinbase one.\n"
" \"trusted\": xxx, (bool) Only present if we consider transaction to be trusted and so safe to spend from.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n"
+ " \"blockheight\": n, (numeric) The block height containing the transaction.\n"
" \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n"
- " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
+ " \"blocktime\": xxx, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + ".\n"
" \"txid\": \"transactionid\", (string) The transaction id.\n"
" \"walletconflicts\": [ (array) Conflicting transaction ids.\n"
" \"txid\", (string) The transaction id.\n"
" ...\n"
" ],\n"
- " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
- " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n"
+ " \"time\": xxx, (numeric) The transaction time expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"timereceived\": xxx, (numeric) The time received expressed in " + UNIX_EPOCH_TIME + ".\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n"
" \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
" may be unknown for unconfirmed transactions not in the mempool\n";
@@ -1591,7 +1613,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
CWalletTx tx = pairWtx.second;
- if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) {
+ if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) {
ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
}
}
@@ -1708,7 +1730,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
}
const CWalletTx& wtx = it->second;
- CAmount nCredit = wtx.GetCredit(*locked_chain, filter);
+ CAmount nCredit = wtx.GetCredit(filter);
CAmount nDebit = wtx.GetDebit(filter);
CAmount nNet = nCredit - nDebit;
CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0);
@@ -1772,7 +1794,7 @@ static UniValue abandontransaction(const JSONRPCRequest& request)
if (!pwallet->mapWallet.count(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
}
- if (!pwallet->AbandonTransaction(*locked_chain, hash)) {
+ if (!pwallet->AbandonTransaction(hash)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
}
@@ -2203,7 +2225,7 @@ static UniValue lockunspent(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
}
- if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) {
+ if (pwallet->IsSpent(outpt.hash, outpt.n)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
}
@@ -2379,7 +2401,8 @@ static UniValue getbalances(const JSONRPCRequest& request)
}
balances.pushKV("mine", balances_mine);
}
- if (wallet.HaveWatchOnly()) {
+ auto spk_man = wallet.GetLegacyScriptPubKeyMan();
+ if (spk_man && spk_man->HaveWatchOnly()) {
UniValue balances_watchonly{UniValue::VOBJ};
balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted));
balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending));
@@ -2409,10 +2432,10 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
" \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n"
" \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n"
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
+ " \"keypoololdest\": xxxxxx, (numeric) the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
" \"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"
+ " \"unlocked_until\": ttt, (numeric) the " + UNIX_EPOCH_TIME + " until which 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"
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
" \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
@@ -2449,7 +2472,15 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
- CKeyID seed_id = pwallet->GetHDChain().seed_id;
+
+ LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
+ if (spk_man) {
+ CKeyID seed_id = spk_man->GetHDChain().seed_id;
+ if (!seed_id.IsNull()) {
+ obj.pushKV("hdseedid", seed_id.GetHex());
+ }
+ }
+
if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
}
@@ -2457,9 +2488,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
obj.pushKV("unlocked_until", pwallet->nRelockTime);
}
obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
- if (!seed_id.IsNull()) {
- obj.pushKV("hdseedid", seed_id.GetHex());
- }
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
if (pwallet->IsScanning()) {
@@ -2574,7 +2602,7 @@ static UniValue loadwallet(const JSONRPCRequest& request)
std::string error;
std::vector<std::string> warning;
- std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_interfaces->chain, location, error, warning);
+ std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_chain, location, error, warning);
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error);
UniValue obj(UniValue::VOBJ);
@@ -2700,7 +2728,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
std::string error;
std::shared_ptr<CWallet> wallet;
- WalletCreationStatus status = CreateWallet(*g_rpc_interfaces->chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet);
+ WalletCreationStatus status = CreateWallet(*g_rpc_chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet);
switch (status) {
case WalletCreationStatus::CREATION_FAILED:
throw JSONRPCError(RPC_WALLET_ERROR, error);
@@ -2903,7 +2931,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
CTxDestination address;
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
- bool reused = avoid_reuse && pwallet->IsUsedDestination(address);
+ bool reused = avoid_reuse && pwallet->IsUsedDestination(out.tx->GetHash(), out.i);
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
continue;
@@ -2920,33 +2948,36 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("label", i->second.name);
}
- if (scriptPubKey.IsPayToScriptHash()) {
- const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
- CScript redeemScript;
- if (pwallet->GetCScript(hash, redeemScript)) {
- entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
- // Now check if the redeemScript is actually a P2WSH script
- CTxDestination witness_destination;
- if (redeemScript.IsPayToWitnessScriptHash()) {
- bool extracted = ExtractDestination(redeemScript, witness_destination);
- assert(extracted);
- // Also return the witness script
- const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination);
- CScriptID id;
- CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
- CScript witnessScript;
- if (pwallet->GetCScript(id, witnessScript)) {
- entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
+ if (provider) {
+ if (scriptPubKey.IsPayToScriptHash()) {
+ const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
+ CScript redeemScript;
+ if (provider->GetCScript(hash, redeemScript)) {
+ entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()));
+ // Now check if the redeemScript is actually a P2WSH script
+ CTxDestination witness_destination;
+ if (redeemScript.IsPayToWitnessScriptHash()) {
+ bool extracted = ExtractDestination(redeemScript, witness_destination);
+ CHECK_NONFATAL(extracted);
+ // Also return the witness script
+ const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination);
+ CScriptID id;
+ CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
+ CScript witnessScript;
+ if (provider->GetCScript(id, witnessScript)) {
+ entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ }
}
}
- }
- } else if (scriptPubKey.IsPayToWitnessScriptHash()) {
- const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address);
- CScriptID id;
- CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
- CScript witnessScript;
- if (pwallet->GetCScript(id, witnessScript)) {
- entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ } else if (scriptPubKey.IsPayToWitnessScriptHash()) {
+ const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address);
+ CScriptID id;
+ CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
+ CScript witnessScript;
+ if (provider->GetCScript(id, witnessScript)) {
+ entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end()));
+ }
}
}
}
@@ -2957,8 +2988,11 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable);
if (out.fSolvable) {
- auto descriptor = InferDescriptor(scriptPubKey, *pwallet);
- entry.pushKV("desc", descriptor->ToString());
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
+ if (provider) {
+ auto descriptor = InferDescriptor(scriptPubKey, *provider);
+ entry.pushKV("desc", descriptor->ToString());
+ }
}
if (avoid_reuse) entry.pushKV("reused", reused);
entry.pushKV("safe", out.fSafe);
@@ -3267,7 +3301,23 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
// Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins);
- return SignTransaction(mtx, pwallet, coins, request.params[2]);
+ std::set<std::shared_ptr<SigningProvider>> providers;
+ for (const std::pair<COutPoint, Coin> coin_pair : coins) {
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(coin_pair.second.out.scriptPubKey);
+ if (provider) {
+ providers.insert(std::move(provider));
+ }
+ }
+ if (providers.size() == 0) {
+ // When there are no available providers, use a dummy SigningProvider so we can check if the tx is complete
+ providers.insert(std::make_shared<SigningProvider>());
+ }
+
+ UniValue result(UniValue::VOBJ);
+ for (std::shared_ptr<SigningProvider> provider : providers) {
+ SignTransaction(mtx, provider.get(), coins, request.params[2], result);
+ }
+ return result;
}
static UniValue bumpfee(const JSONRPCRequest& request)
@@ -3319,10 +3369,11 @@ static UniValue bumpfee(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
- " \"txid\": \"value\", (string) The id of the new transaction\n"
- " \"origfee\": n, (numeric) Fee of the replaced transaction\n"
- " \"fee\": n, (numeric) Fee of the new transaction\n"
- " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n"
+ " \"psbt\": \"psbt\", (string) The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.\n"
+ " \"txid\": \"value\", (string) The id of the new transaction. Only returned when wallet private keys are enabled.\n"
+ " \"origfee\": n, (numeric) The fee of the replaced transaction.\n"
+ " \"fee\": n, (numeric) The fee of the new transaction.\n"
+ " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty).\n"
"}\n"
},
RPCExamples{
@@ -3334,10 +3385,12 @@ static UniValue bumpfee(const JSONRPCRequest& request)
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ});
uint256 hash(ParseHashV(request.params[0], "txid"));
+ CCoinControl coin_control;
+ coin_control.fAllowWatchOnly = pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
// optional parameters
CAmount totalFee = 0;
- CCoinControl coin_control;
coin_control.m_signal_bip125_rbf = true;
+
if (!request.params[1].isNull()) {
UniValue options = request.params[1];
RPCTypeCheckObj(options,
@@ -3422,17 +3475,32 @@ static UniValue bumpfee(const JSONRPCRequest& request)
}
}
- // sign bumped transaction
- if (!feebumper::SignTransaction(*pwallet, mtx)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
- }
- // commit the bumped transaction
- uint256 txid;
- if (feebumper::CommitTransaction(*pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) {
- throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
- }
UniValue result(UniValue::VOBJ);
- result.pushKV("txid", txid.GetHex());
+
+ // If wallet private keys are enabled, return the new transaction id,
+ // otherwise return the base64-encoded unsigned PSBT of the new transaction.
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (!feebumper::SignTransaction(*pwallet, mtx)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ }
+
+ uint256 txid;
+ if (feebumper::CommitTransaction(*pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) {
+ throw JSONRPCError(RPC_WALLET_ERROR, errors[0]);
+ }
+
+ result.pushKV("txid", txid.GetHex());
+ } else {
+ PartiallySignedTransaction psbtx(mtx);
+ bool complete = false;
+ const TransactionError err = FillPSBT(pwallet, psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
+ CHECK_NONFATAL(err == TransactionError::OK);
+ CHECK_NONFATAL(!complete);
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ result.pushKV("psbt", EncodeBase64(ssTx.str()));
+ }
+
result.pushKV("origfee", ValueFromAmount(old_fee));
result.pushKV("fee", ValueFromAmount(new_fee));
UniValue result_errors(UniValue::VARR);
@@ -3539,7 +3607,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request)
class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue>
{
public:
- CWallet * const pwallet;
+ const SigningProvider * const provider;
void ProcessSubScript(const CScript& subscript, UniValue& obj) const
{
@@ -3575,7 +3643,7 @@ public:
}
}
- explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {}
+ explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
@@ -3584,7 +3652,7 @@ public:
CKeyID keyID(pkhash);
UniValue obj(UniValue::VOBJ);
CPubKey vchPubKey;
- if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
+ if (provider && provider->GetPubKey(keyID, vchPubKey)) {
obj.pushKV("pubkey", HexStr(vchPubKey));
obj.pushKV("iscompressed", vchPubKey.IsCompressed());
}
@@ -3596,7 +3664,7 @@ public:
CScriptID scriptID(scripthash);
UniValue obj(UniValue::VOBJ);
CScript subscript;
- if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
+ if (provider && provider->GetCScript(scriptID, subscript)) {
ProcessSubScript(subscript, obj);
}
return obj;
@@ -3606,7 +3674,7 @@ public:
{
UniValue obj(UniValue::VOBJ);
CPubKey pubkey;
- if (pwallet && pwallet->GetPubKey(CKeyID(id), pubkey)) {
+ if (provider && provider->GetPubKey(CKeyID(id), pubkey)) {
obj.pushKV("pubkey", HexStr(pubkey));
}
return obj;
@@ -3619,7 +3687,7 @@ public:
CRIPEMD160 hasher;
uint160 hash;
hasher.Write(id.begin(), 32).Finalize(hash.begin());
- if (pwallet && pwallet->GetCScript(CScriptID(hash), subscript)) {
+ if (provider && provider->GetCScript(CScriptID(hash), subscript)) {
ProcessSubScript(subscript, obj);
}
return obj;
@@ -3632,8 +3700,13 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de
{
UniValue ret(UniValue::VOBJ);
UniValue detail = DescribeAddress(dest);
+ CScript script = GetScriptForDestination(dest);
+ std::unique_ptr<SigningProvider> provider = nullptr;
+ if (pwallet) {
+ provider = pwallet->GetSigningProvider(script);
+ }
ret.pushKVs(detail);
- ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest));
+ ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider.get()), dest));
return ret;
}
@@ -3657,53 +3730,62 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
return NullUniValue;
}
+ const std::string example_address = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\"";
+
RPCHelpMan{"getaddressinfo",
- "\nReturn information about the given bitcoin address. Some information requires the address\n"
- "to be in the wallet.\n",
+ "\nReturn information about the given bitcoin address.\n"
+ "Some of the information will only be present if the address is in the active wallet.\n",
{
- {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."},
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."},
},
RPCResult{
"{\n"
- " \"address\" : \"address\", (string) The bitcoin address validated\n"
- " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n"
- " \"ismine\" : true|false, (boolean) If the address is yours or not\n"
- " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n"
- " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n"
- " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n"
- " \"isscript\" : true|false, (boolean) If the key is a script\n"
- " \"ischange\" : true|false, (boolean) If the address was used for change output\n"
- " \"iswitness\" : true|false, (boolean) If the address is a witness address\n"
- " \"witness_version\" : version (numeric, optional) The version number of the witness program\n"
- " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n"
- " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n"
- " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n"
- " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n"
+ " \"address\" : \"address\", (string) The bitcoin address validated.\n"
+ " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address.\n"
+ " \"ismine\" : true|false, (boolean) If the address is yours.\n"
+ " \"iswatchonly\" : true|false, (boolean) If the address is watchonly.\n"
+ " \"solvable\" : true|false, (boolean) If we know how to spend coins sent to this address, ignoring the possible lack of private keys.\n"
+ " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable).\n"
+ " \"isscript\" : true|false, (boolean) If the key is a script.\n"
+ " \"ischange\" : true|false, (boolean) If the address was used for change output.\n"
+ " \"iswitness\" : true|false, (boolean) If the address is a witness address.\n"
+ " \"witness_version\" : version (numeric, optional) The version number of the witness program.\n"
+ " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program.\n"
+ " \"script\" : \"type\" (string, optional) The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
+ " types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
+ " witness_v0_scripthash, witness_unknown.\n"
+ " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address.\n"
+ " \"pubkeys\" (array, optional) Array of pubkeys associated with the known redeemscript (only if script is multisig).\n"
" [\n"
- " \"pubkey\"\n"
+ " \"pubkey\" (string)\n"
" ,...\n"
" ]\n"
- " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n"
- " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n"
- " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n"
- " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n"
- " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n"
- " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
- " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
- " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n"
- " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n"
- " \"labels\" (object) Array of labels associated with the address.\n"
+ " \"sigsrequired\" : xxxxx (numeric, optional) The number of signatures required to spend multisig output (only if script is multisig).\n"
+ " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).\n"
+ " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. Includes all\n"
+ " getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath,\n"
+ " hdseedid) and relation to the wallet (ismine, iswatchonly).\n"
+ " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed.\n"
+ " \"label\" : \"label\" (string) DEPRECATED. The label associated with the address. Defaults to \"\". Replaced by the labels array below.\n"
+ " \"timestamp\" : timestamp, (number, optional) The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + ".\n"
+ " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath, if the key is HD and available.\n"
+ " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed.\n"
+ " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingerprint of the master key.\n"
+ " \"labels\" (array) Array of labels associated with the address. Currently limited to one label but returned\n"
+ " as an array to keep the API stable if multiple labels are enabled in the future.\n"
" [\n"
- " { (json object of label data)\n"
- " \"name\": \"labelname\" (string) The label\n"
- " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
- " },...\n"
+ " \"label name\" (string) The label name. Defaults to \"\".\n"
+ " DEPRECATED, will be removed in 0.21. To re-enable, launch bitcoind with `-deprecatedrpc=labelspurpose`:\n"
+ " {\n"
+ " \"name\" : \"label name\" (string) The label name. Defaults to \"\".\n"
+ " \"purpose\" : \"purpose\" (string) The purpose of the associated address (send or receive).\n"
+ " }\n"
" ]\n"
"}\n"
},
RPCExamples{
- HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
- + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
+ HelpExampleCli("getaddressinfo", example_address) +
+ HelpExampleRpc("getaddressinfo", example_address)
},
}.Check(request);
@@ -3711,7 +3793,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
UniValue ret(UniValue::VOBJ);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
-
// Make sure the destination is valid
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
@@ -3723,50 +3804,59 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
- isminetype mine = IsMine(*pwallet, dest);
+ std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
+
+ isminetype mine = pwallet->IsMine(dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
- bool solvable = IsSolvable(*pwallet, scriptPubKey);
+
+ bool solvable = provider && IsSolvable(*provider, scriptPubKey);
ret.pushKV("solvable", solvable);
+
if (solvable) {
- ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString());
+ ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString());
}
+
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
+
UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail);
- if (pwallet->mapAddressBook.count(dest)) {
+
+ // DEPRECATED: Return label field if existing. Currently only one label can
+ // be associated with an address, so the label should be equivalent to the
+ // value of the name key/value pair in the labels array below.
+ if ((pwallet->chain().rpcEnableDeprecated("label")) && (pwallet->mapAddressBook.count(dest))) {
ret.pushKV("label", pwallet->mapAddressBook[dest].name);
}
+
ret.pushKV("ischange", pwallet->IsChange(scriptPubKey));
- const CKeyMetadata* meta = nullptr;
- CKeyID key_id = GetKeyForDestination(*pwallet, dest);
- if (!key_id.IsNull()) {
- auto it = pwallet->mapKeyMetadata.find(key_id);
- if (it != pwallet->mapKeyMetadata.end()) {
- meta = &it->second;
- }
- }
- if (!meta) {
- auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey));
- if (it != pwallet->m_script_metadata.end()) {
- meta = &it->second;
- }
- }
- if (meta) {
- ret.pushKV("timestamp", meta->nCreateTime);
- if (meta->has_key_origin) {
- ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
- ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
- ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
+
+ ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
+ if (spk_man) {
+ if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) {
+ ret.pushKV("timestamp", meta->nCreateTime);
+ if (meta->has_key_origin) {
+ ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
+ ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
+ ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4));
+ }
}
}
- // Currently only one label can be associated with an address, return an array
- // so the API remains stable if we allow multiple labels to be associated with
- // an address.
+ // Return a `labels` array containing the label associated with the address,
+ // equivalent to the `label` field above. Currently only one label can be
+ // associated with an address, but we return an array so the API remains
+ // stable if we allow multiple labels to be associated with an address in
+ // the future.
UniValue labels(UniValue::VARR);
std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest);
if (mi != pwallet->mapAddressBook.end()) {
- labels.push_back(AddressBookDataToJSON(mi->second, true));
+ // DEPRECATED: The previous behavior of returning an array containing a
+ // JSON object of `name` and `purpose` key/value pairs is deprecated.
+ if (pwallet->chain().rpcEnableDeprecated("labelspurpose")) {
+ labels.push_back(AddressBookDataToJSON(mi->second, true));
+ } else {
+ labels.push_back(mi->second.name);
+ }
}
ret.pushKV("labels", std::move(labels));
@@ -3814,7 +3904,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request)
// address strings, but build a separate set as a precaution just in
// case it does.
bool unique = addresses.emplace(address).second;
- assert(unique);
+ CHECK_NONFATAL(unique);
// UniValue::pushKV checks if the key exists in O(N)
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::__pushKV is used instead,
@@ -3916,6 +4006,8 @@ UniValue sethdseed(const JSONRPCRequest& request)
},
}.Check(request);
+ LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
+
if (pwallet->chain().isInitialBlockDownload()) {
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download");
}
@@ -3925,7 +4017,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
}
auto locked_chain = pwallet->chain().lock();
- LOCK(pwallet->cs_wallet);
+ LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
// Do not do anything to non-HD wallets
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
@@ -3941,22 +4033,22 @@ UniValue sethdseed(const JSONRPCRequest& request)
CPubKey master_pub_key;
if (request.params[1].isNull()) {
- master_pub_key = pwallet->GenerateNewSeed();
+ master_pub_key = spk_man.GenerateNewSeed();
} else {
CKey key = DecodeSecret(request.params[1].get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
}
- if (HaveKey(*pwallet, key)) {
+ if (HaveKey(spk_man, key)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
}
- master_pub_key = pwallet->DeriveNewSeed(key);
+ master_pub_key = spk_man.DeriveNewSeed(key);
}
- pwallet->SetHDSeed(master_pub_key);
- if (flush_key_pool) pwallet->NewKeyPool();
+ spk_man.SetHDSeed(master_pub_key);
+ if (flush_key_pool) spk_man.NewKeyPool();
return NullUniValue;
}
@@ -4231,3 +4323,5 @@ void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
handlers.emplace_back(chain.handleRpc(commands[vcidx]));
}
+
+interfaces::Chain* g_rpc_chain = nullptr;
diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h
index 1c0523c90b..2813fa2bfc 100644
--- a/src/wallet/rpcwallet.h
+++ b/src/wallet/rpcwallet.h
@@ -12,6 +12,7 @@
class CRPCTable;
class CWallet;
class JSONRPCRequest;
+class LegacyScriptPubKeyMan;
class UniValue;
struct PartiallySignedTransaction;
class CTransaction;
@@ -21,6 +22,12 @@ class Chain;
class Handler;
}
+//! Pointer to chain interface that needs to be declared as a global to be
+//! accessible loadwallet and createwallet methods. Due to limitations of the
+//! RPC framework, there's currently no direct way to pass in state to RPC
+//! methods without globals.
+extern interfaces::Chain* g_rpc_chain;
+
void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique_ptr<interfaces::Handler>>& handlers);
/**
@@ -34,6 +41,7 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques
std::string HelpRequiringPassphrase(const CWallet*);
void EnsureWalletIsUnlocked(const CWallet*);
bool EnsureWalletIsAvailable(const CWallet*, bool avoidException);
+LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create = false);
UniValue getaddressinfo(const JSONRPCRequest& request);
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
new file mode 100644
index 0000000000..4c9d88973e
--- /dev/null
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -0,0 +1,1428 @@
+// Copyright (c) 2019 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 <key_io.h>
+#include <outputtype.h>
+#include <script/descriptor.h>
+#include <util/bip32.h>
+#include <util/strencodings.h>
+#include <util/translation.h>
+#include <wallet/scriptpubkeyman.h>
+
+bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
+{
+ LOCK(cs_KeyStore);
+ error.clear();
+
+ // Generate a new key that is added to wallet
+ CPubKey new_key;
+ if (!GetKeyFromPool(new_key, type)) {
+ error = "Error: Keypool ran out, please call keypoolrefill first";
+ return false;
+ }
+ LearnRelatedScripts(new_key, type);
+ dest = GetDestinationForKey(new_key, type);
+ return true;
+}
+
+typedef std::vector<unsigned char> valtype;
+
+namespace {
+
+/**
+ * This is an enum that tracks the execution context of a script, similar to
+ * SigVersion in script/interpreter. It is separate however because we want to
+ * distinguish between top-level scriptPubKey execution and P2SH redeemScript
+ * execution (a distinction that has no impact on consensus rules).
+ */
+enum class IsMineSigVersion
+{
+ TOP = 0, //!< scriptPubKey execution
+ P2SH = 1, //!< P2SH redeemScript
+ WITNESS_V0 = 2, //!< P2WSH witness script execution
+};
+
+/**
+ * This is an internal representation of isminetype + invalidity.
+ * Its order is significant, as we return the max of all explored
+ * possibilities.
+ */
+enum class IsMineResult
+{
+ NO = 0, //!< Not ours
+ WATCH_ONLY = 1, //!< Included in watch-only balance
+ SPENDABLE = 2, //!< Included in all balances
+ INVALID = 3, //!< Not spendable by anyone (uncompressed pubkey in segwit, P2SH inside P2SH or witness, witness inside witness)
+};
+
+bool PermitsUncompressed(IsMineSigVersion sigversion)
+{
+ return sigversion == IsMineSigVersion::TOP || sigversion == IsMineSigVersion::P2SH;
+}
+
+bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan& keystore)
+{
+ for (const valtype& pubkey : pubkeys) {
+ CKeyID keyID = CPubKey(pubkey).GetID();
+ if (!keystore.HaveKey(keyID)) return false;
+ }
+ return true;
+}
+
+IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& scriptPubKey, IsMineSigVersion sigversion)
+{
+ IsMineResult ret = IsMineResult::NO;
+
+ std::vector<valtype> vSolutions;
+ txnouttype whichType = Solver(scriptPubKey, vSolutions);
+
+ CKeyID keyID;
+ switch (whichType)
+ {
+ case TX_NONSTANDARD:
+ case TX_NULL_DATA:
+ case TX_WITNESS_UNKNOWN:
+ break;
+ case TX_PUBKEY:
+ keyID = CPubKey(vSolutions[0]).GetID();
+ if (!PermitsUncompressed(sigversion) && vSolutions[0].size() != 33) {
+ return IsMineResult::INVALID;
+ }
+ if (keystore.HaveKey(keyID)) {
+ ret = std::max(ret, IsMineResult::SPENDABLE);
+ }
+ break;
+ case TX_WITNESS_V0_KEYHASH:
+ {
+ if (sigversion == IsMineSigVersion::WITNESS_V0) {
+ // P2WPKH inside P2WSH is invalid.
+ return IsMineResult::INVALID;
+ }
+ if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
+ // We do not support bare witness outputs unless the P2SH version of it would be
+ // acceptable as well. This protects against matching before segwit activates.
+ // This also applies to the P2WSH case.
+ break;
+ }
+ ret = std::max(ret, IsMineInner(keystore, GetScriptForDestination(PKHash(uint160(vSolutions[0]))), IsMineSigVersion::WITNESS_V0));
+ break;
+ }
+ case TX_PUBKEYHASH:
+ keyID = CKeyID(uint160(vSolutions[0]));
+ if (!PermitsUncompressed(sigversion)) {
+ CPubKey pubkey;
+ if (keystore.GetPubKey(keyID, pubkey) && !pubkey.IsCompressed()) {
+ return IsMineResult::INVALID;
+ }
+ }
+ if (keystore.HaveKey(keyID)) {
+ ret = std::max(ret, IsMineResult::SPENDABLE);
+ }
+ break;
+ case TX_SCRIPTHASH:
+ {
+ if (sigversion != IsMineSigVersion::TOP) {
+ // P2SH inside P2WSH or P2SH is invalid.
+ return IsMineResult::INVALID;
+ }
+ CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
+ CScript subscript;
+ if (keystore.GetCScript(scriptID, subscript)) {
+ ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::P2SH));
+ }
+ break;
+ }
+ case TX_WITNESS_V0_SCRIPTHASH:
+ {
+ if (sigversion == IsMineSigVersion::WITNESS_V0) {
+ // P2WSH inside P2WSH is invalid.
+ return IsMineResult::INVALID;
+ }
+ if (sigversion == IsMineSigVersion::TOP && !keystore.HaveCScript(CScriptID(CScript() << OP_0 << vSolutions[0]))) {
+ break;
+ }
+ uint160 hash;
+ CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(hash.begin());
+ CScriptID scriptID = CScriptID(hash);
+ CScript subscript;
+ if (keystore.GetCScript(scriptID, subscript)) {
+ ret = std::max(ret, IsMineInner(keystore, subscript, IsMineSigVersion::WITNESS_V0));
+ }
+ break;
+ }
+
+ case TX_MULTISIG:
+ {
+ // Never treat bare multisig outputs as ours (they can still be made watchonly-though)
+ if (sigversion == IsMineSigVersion::TOP) {
+ break;
+ }
+
+ // Only consider transactions "mine" if we own ALL the
+ // keys involved. Multi-signature transactions that are
+ // partially owned (somebody else has a key that can spend
+ // them) enable spend-out-from-under-you attacks, especially
+ // in shared-wallet situations.
+ std::vector<valtype> keys(vSolutions.begin()+1, vSolutions.begin()+vSolutions.size()-1);
+ if (!PermitsUncompressed(sigversion)) {
+ for (size_t i = 0; i < keys.size(); i++) {
+ if (keys[i].size() != 33) {
+ return IsMineResult::INVALID;
+ }
+ }
+ }
+ if (HaveKeys(keys, keystore)) {
+ ret = std::max(ret, IsMineResult::SPENDABLE);
+ }
+ break;
+ }
+ }
+
+ if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
+ ret = std::max(ret, IsMineResult::WATCH_ONLY);
+ }
+ return ret;
+}
+
+} // namespace
+
+isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const
+{
+ switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) {
+ case IsMineResult::INVALID:
+ case IsMineResult::NO:
+ return ISMINE_NO;
+ case IsMineResult::WATCH_ONLY:
+ return ISMINE_WATCH_ONLY;
+ case IsMineResult::SPENDABLE:
+ return ISMINE_SPENDABLE;
+ }
+ assert(false);
+}
+
+bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys)
+{
+ {
+ LOCK(cs_KeyStore);
+ assert(mapKeys.empty());
+
+ bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
+ bool keyFail = false;
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
+ for (; mi != mapCryptedKeys.end(); ++mi)
+ {
+ const CPubKey &vchPubKey = (*mi).second.first;
+ const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
+ CKey key;
+ if (!DecryptKey(master_key, vchCryptedSecret, vchPubKey, key))
+ {
+ keyFail = true;
+ break;
+ }
+ keyPass = true;
+ if (fDecryptionThoroughlyChecked)
+ break;
+ }
+ if (keyPass && keyFail)
+ {
+ LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
+ throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt.");
+ }
+ if (keyFail || (!keyPass && !accept_no_keys))
+ return false;
+ fDecryptionThoroughlyChecked = true;
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch)
+{
+ LOCK(cs_KeyStore);
+ encrypted_batch = batch;
+ if (!mapCryptedKeys.empty()) {
+ encrypted_batch = nullptr;
+ return false;
+ }
+
+ KeyMap keys_to_encrypt;
+ keys_to_encrypt.swap(mapKeys); // Clear mapKeys so AddCryptedKeyInner will succeed.
+ for (const KeyMap::value_type& mKey : keys_to_encrypt)
+ {
+ const CKey &key = mKey.second;
+ CPubKey vchPubKey = key.GetPubKey();
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ std::vector<unsigned char> vchCryptedSecret;
+ if (!EncryptSecret(master_key, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) {
+ encrypted_batch = nullptr;
+ return false;
+ }
+ if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) {
+ encrypted_batch = nullptr;
+ return false;
+ }
+ }
+ encrypted_batch = nullptr;
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool)
+{
+ LOCK(cs_KeyStore);
+ if (!CanGetAddresses(internal)) {
+ return false;
+ }
+
+ if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
+ return false;
+ }
+ address = GetDestinationForKey(keypool.vchPubKey, type);
+ return true;
+}
+
+void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
+{
+ LOCK(cs_KeyStore);
+ // extract addresses and check if they match with an unused keypool key
+ for (const auto& keyid : GetAffectedKeys(script, *this)) {
+ std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
+ if (mi != m_pool_key_to_index.end()) {
+ WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
+ MarkReserveKeysAsUsed(mi->second);
+
+ if (!TopUp()) {
+ WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
+ }
+ }
+ }
+}
+
+void LegacyScriptPubKeyMan::UpgradeKeyMetadata()
+{
+ LOCK(cs_KeyStore);
+ if (m_storage.IsLocked() || m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
+ return;
+ }
+
+ std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(m_storage.GetDatabase());
+ for (auto& meta_pair : mapKeyMetadata) {
+ CKeyMetadata& meta = meta_pair.second;
+ if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin
+ CKey key;
+ GetKey(meta.hd_seed_id, key);
+ CExtKey masterKey;
+ masterKey.SetSeed(key.begin(), key.size());
+ // Add to map
+ CKeyID master_id = masterKey.key.GetPubKey().GetID();
+ std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
+ if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) {
+ throw std::runtime_error("Invalid stored hdKeypath");
+ }
+ meta.has_key_origin = true;
+ if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
+ meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
+ }
+
+ // Write meta to wallet
+ CPubKey pubkey;
+ if (GetPubKey(meta_pair.first, pubkey)) {
+ batch->WriteKeyMetadata(meta, pubkey, true);
+ }
+ }
+ }
+}
+
+bool LegacyScriptPubKeyMan::SetupGeneration(bool force)
+{
+ if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) {
+ return false;
+ }
+
+ SetHDSeed(GenerateNewSeed());
+ if (!NewKeyPool()) {
+ return false;
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::IsHDEnabled() const
+{
+ return !hdChain.seed_id.IsNull();
+}
+
+bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal)
+{
+ LOCK(cs_KeyStore);
+ // Check if the keypool has keys
+ bool keypool_has_keys;
+ if (internal && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
+ keypool_has_keys = setInternalKeyPool.size() > 0;
+ } else {
+ keypool_has_keys = KeypoolCountExternalKeys() > 0;
+ }
+ // If the keypool doesn't have keys, check if we can generate them
+ if (!keypool_has_keys) {
+ return CanGenerateKeys();
+ }
+ return keypool_has_keys;
+}
+
+bool LegacyScriptPubKeyMan::Upgrade(int prev_version, std::string& error)
+{
+ LOCK(cs_KeyStore);
+ error = "";
+ bool hd_upgrade = false;
+ bool split_upgrade = false;
+ if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) {
+ WalletLogPrintf("Upgrading wallet to HD\n");
+ m_storage.SetMinVersion(FEATURE_HD);
+
+ // generate a new master key
+ CPubKey masterPubKey = GenerateNewSeed();
+ SetHDSeed(masterPubKey);
+ hd_upgrade = true;
+ }
+ // Upgrade to HD chain split if necessary
+ if (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) && CHDChain::VERSION_HD_CHAIN_SPLIT) {
+ WalletLogPrintf("Upgrading wallet to use HD chain split\n");
+ m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
+ split_upgrade = FEATURE_HD_SPLIT > prev_version;
+ }
+ // Mark all keys currently in the keypool as pre-split
+ if (split_upgrade) {
+ MarkPreSplitKeys();
+ }
+ // Regenerate the keypool if upgraded to HD
+ if (hd_upgrade) {
+ if (!TopUp()) {
+ error = _("Unable to generate keys").translated;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::HavePrivateKeys() const
+{
+ LOCK(cs_KeyStore);
+ return !mapKeys.empty() || !mapCryptedKeys.empty();
+}
+
+void LegacyScriptPubKeyMan::RewriteDB()
+{
+ LOCK(cs_KeyStore);
+ setInternalKeyPool.clear();
+ setExternalKeyPool.clear();
+ m_pool_key_to_index.clear();
+ // Note: can't top-up keypool here, because wallet is locked.
+ // User will be prompted to unlock wallet the next operation
+ // that requires a new key.
+}
+
+static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) {
+ if (setKeyPool.empty()) {
+ return GetTime();
+ }
+
+ CKeyPool keypool;
+ int64_t nIndex = *(setKeyPool.begin());
+ if (!batch.ReadPool(nIndex, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
+ }
+ assert(keypool.vchPubKey.IsValid());
+ return keypool.nTime;
+}
+
+int64_t LegacyScriptPubKeyMan::GetOldestKeyPoolTime()
+{
+ LOCK(cs_KeyStore);
+
+ WalletBatch batch(m_storage.GetDatabase());
+
+ // load oldest key from keypool, get time and return
+ int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch);
+ if (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
+ oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey);
+ if (!set_pre_split_keypool.empty()) {
+ oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey);
+ }
+ }
+
+ return oldestKey;
+}
+
+size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys()
+{
+ LOCK(cs_KeyStore);
+ return setExternalKeyPool.size() + set_pre_split_keypool.size();
+}
+
+unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const
+{
+ LOCK(cs_KeyStore);
+ return setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size();
+}
+
+int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const
+{
+ LOCK(cs_KeyStore);
+ return nTimeFirstKey;
+}
+
+std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSigningProvider(const CScript& script) const
+{
+ return MakeUnique<LegacySigningProvider>(*this);
+}
+
+bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sigdata)
+{
+ if (IsMine(script) != ISMINE_NO) {
+ // If it IsMine, we can always provide in some way
+ return true;
+ } else if (HaveCScript(CScriptID(script))) {
+ // We can still provide some stuff if we have the script, but IsMine failed because we don't have keys
+ return true;
+ } else {
+ // If, given the stuff in sigdata, we could make a valid sigature, then we can provide for this script
+ ProduceSignature(*this, DUMMY_SIGNATURE_CREATOR, script, sigdata);
+ if (!sigdata.signatures.empty()) {
+ // If we could make signatures, make sure we have a private key to actually make a signature
+ bool has_privkeys = false;
+ for (const auto& key_sig_pair : sigdata.signatures) {
+ has_privkeys |= HaveKey(key_sig_pair.first);
+ }
+ return has_privkeys;
+ }
+ return false;
+ }
+}
+
+const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
+{
+ LOCK(cs_KeyStore);
+
+ CKeyID key_id = GetKeyForDestination(*this, dest);
+ if (!key_id.IsNull()) {
+ auto it = mapKeyMetadata.find(key_id);
+ if (it != mapKeyMetadata.end()) {
+ return &it->second;
+ }
+ }
+
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ auto it = m_script_metadata.find(CScriptID(scriptPubKey));
+ if (it != m_script_metadata.end()) {
+ return &it->second;
+ }
+
+ return nullptr;
+}
+
+uint256 LegacyScriptPubKeyMan::GetID() const
+{
+ return UINT256_ONE();
+}
+
+/**
+ * Update wallet first key creation time. This should be called whenever keys
+ * are added to the wallet, with the oldest key creation time.
+ */
+void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime)
+{
+ AssertLockHeld(cs_KeyStore);
+ if (nCreateTime <= 1) {
+ // Cannot determine birthday information, so set the wallet birthday to
+ // the beginning of time.
+ nTimeFirstKey = 1;
+ } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
+ nTimeFirstKey = nCreateTime;
+ }
+}
+
+bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey)
+{
+ return AddKeyPubKeyInner(key, pubkey);
+}
+
+bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
+{
+ LOCK(cs_KeyStore);
+ WalletBatch batch(m_storage.GetDatabase());
+ return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey);
+}
+
+bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey)
+{
+ AssertLockHeld(cs_KeyStore);
+
+ // Make sure we aren't adding private keys to private key disabled wallets
+ assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+
+ // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey
+ // which is overridden below. To avoid flushes, the database handle is
+ // tunneled through to it.
+ bool needsDB = !encrypted_batch;
+ if (needsDB) {
+ encrypted_batch = &batch;
+ }
+ if (!AddKeyPubKeyInner(secret, pubkey)) {
+ if (needsDB) encrypted_batch = nullptr;
+ return false;
+ }
+ if (needsDB) encrypted_batch = nullptr;
+
+ // check if we need to remove from watch-only
+ CScript script;
+ script = GetScriptForDestination(PKHash(pubkey));
+ if (HaveWatchOnly(script)) {
+ RemoveWatchOnly(script);
+ }
+ script = GetScriptForRawPubKey(pubkey);
+ if (HaveWatchOnly(script)) {
+ RemoveWatchOnly(script);
+ }
+
+ if (!m_storage.HasEncryptionKeys()) {
+ return batch.WriteKey(pubkey,
+ secret.GetPrivKey(),
+ mapKeyMetadata[pubkey.GetID()]);
+ }
+ m_storage.UnsetBlankWalletFlag(batch);
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::LoadCScript(const CScript& redeemScript)
+{
+ /* A sanity check was added in pull #3843 to avoid adding redeemScripts
+ * that never can be redeemed. However, old wallets may still contain
+ * these. Do not add them to the wallet and warn. */
+ if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
+ {
+ std::string strAddr = EncodeDestination(ScriptHash(redeemScript));
+ WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
+ return true;
+ }
+
+ return FillableSigningProvider::AddCScript(redeemScript);
+}
+
+void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta)
+{
+ LOCK(cs_KeyStore);
+ UpdateTimeFirstKey(meta.nCreateTime);
+ mapKeyMetadata[keyID] = meta;
+}
+
+void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta)
+{
+ LOCK(cs_KeyStore);
+ UpdateTimeFirstKey(meta.nCreateTime);
+ m_script_metadata[script_id] = meta;
+}
+
+bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey)
+{
+ LOCK(cs_KeyStore);
+ if (!m_storage.HasEncryptionKeys()) {
+ return FillableSigningProvider::AddKeyPubKey(key, pubkey);
+ }
+
+ if (m_storage.IsLocked()) {
+ return false;
+ }
+
+ std::vector<unsigned char> vchCryptedSecret;
+ CKeyingMaterial vchSecret(key.begin(), key.end());
+ if (!EncryptSecret(m_storage.GetEncryptionKey(), vchSecret, pubkey.GetHash(), vchCryptedSecret)) {
+ return false;
+ }
+
+ if (!AddCryptedKey(pubkey, vchCryptedSecret)) {
+ return false;
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
+{
+ return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
+}
+
+bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
+{
+ LOCK(cs_KeyStore);
+ assert(mapKeys.empty());
+
+ mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
+ ImplicitlyLearnRelatedKeyScripts(vchPubKey);
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::AddCryptedKey(const CPubKey &vchPubKey,
+ const std::vector<unsigned char> &vchCryptedSecret)
+{
+ if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret))
+ return false;
+ {
+ LOCK(cs_KeyStore);
+ if (encrypted_batch)
+ return encrypted_batch->WriteCryptedKey(vchPubKey,
+ vchCryptedSecret,
+ mapKeyMetadata[vchPubKey.GetID()]);
+ else
+ return WalletBatch(m_storage.GetDatabase()).WriteCryptedKey(vchPubKey,
+ vchCryptedSecret,
+ mapKeyMetadata[vchPubKey.GetID()]);
+ }
+}
+
+bool LegacyScriptPubKeyMan::HaveWatchOnly(const CScript &dest) const
+{
+ LOCK(cs_KeyStore);
+ return setWatchOnly.count(dest) > 0;
+}
+
+bool LegacyScriptPubKeyMan::HaveWatchOnly() const
+{
+ LOCK(cs_KeyStore);
+ return (!setWatchOnly.empty());
+}
+
+static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
+{
+ std::vector<std::vector<unsigned char>> solutions;
+ return Solver(dest, solutions) == TX_PUBKEY &&
+ (pubKeyOut = CPubKey(solutions[0])).IsFullyValid();
+}
+
+bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest)
+{
+ {
+ LOCK(cs_KeyStore);
+ setWatchOnly.erase(dest);
+ CPubKey pubKey;
+ if (ExtractPubKey(dest, pubKey)) {
+ mapWatchKeys.erase(pubKey.GetID());
+ }
+ // Related CScripts are not removed; having superfluous scripts around is
+ // harmless (see comment in ImplicitlyLearnRelatedKeyScripts).
+ }
+
+ if (!HaveWatchOnly())
+ NotifyWatchonlyChanged(false);
+ if (!WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest))
+ return false;
+
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::LoadWatchOnly(const CScript &dest)
+{
+ return AddWatchOnlyInMem(dest);
+}
+
+bool LegacyScriptPubKeyMan::AddWatchOnlyInMem(const CScript &dest)
+{
+ LOCK(cs_KeyStore);
+ setWatchOnly.insert(dest);
+ CPubKey pubKey;
+ if (ExtractPubKey(dest, pubKey)) {
+ mapWatchKeys[pubKey.GetID()] = pubKey;
+ ImplicitlyLearnRelatedKeyScripts(pubKey);
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest)
+{
+ if (!AddWatchOnlyInMem(dest))
+ return false;
+ const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
+ UpdateTimeFirstKey(meta.nCreateTime);
+ NotifyWatchonlyChanged(true);
+ if (batch.WriteWatchOnly(dest, meta)) {
+ m_storage.UnsetBlankWalletFlag(batch);
+ return true;
+ }
+ return false;
+}
+
+bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time)
+{
+ m_script_metadata[CScriptID(dest)].nCreateTime = create_time;
+ return AddWatchOnlyWithDB(batch, dest);
+}
+
+bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ return AddWatchOnlyWithDB(batch, dest);
+}
+
+bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
+{
+ m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
+ return AddWatchOnly(dest);
+}
+
+void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly)
+{
+ LOCK(cs_KeyStore);
+ if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain))
+ throw std::runtime_error(std::string(__func__) + ": writing chain failed");
+
+ hdChain = chain;
+}
+
+bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const
+{
+ LOCK(cs_KeyStore);
+ if (!m_storage.HasEncryptionKeys()) {
+ return FillableSigningProvider::HaveKey(address);
+ }
+ return mapCryptedKeys.count(address) > 0;
+}
+
+bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const
+{
+ LOCK(cs_KeyStore);
+ if (!m_storage.HasEncryptionKeys()) {
+ return FillableSigningProvider::GetKey(address, keyOut);
+ }
+
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
+ {
+ const CPubKey &vchPubKey = (*mi).second.first;
+ const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
+ return DecryptKey(m_storage.GetEncryptionKey(), vchCryptedSecret, vchPubKey, keyOut);
+ }
+ return false;
+}
+
+bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const
+{
+ CKeyMetadata meta;
+ {
+ LOCK(cs_KeyStore);
+ auto it = mapKeyMetadata.find(keyID);
+ if (it != mapKeyMetadata.end()) {
+ meta = it->second;
+ }
+ }
+ if (meta.has_key_origin) {
+ std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint);
+ info.path = meta.key_origin.path;
+ } else { // Single pubkeys get the master fingerprint of themselves
+ std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint);
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const
+{
+ LOCK(cs_KeyStore);
+ WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
+ if (it != mapWatchKeys.end()) {
+ pubkey_out = it->second;
+ return true;
+ }
+ return false;
+}
+
+bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
+{
+ LOCK(cs_KeyStore);
+ if (!m_storage.HasEncryptionKeys()) {
+ if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) {
+ return GetWatchPubKey(address, vchPubKeyOut);
+ }
+ return true;
+ }
+
+ CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
+ if (mi != mapCryptedKeys.end())
+ {
+ vchPubKeyOut = (*mi).second.first;
+ return true;
+ }
+ // Check for watch-only pubkeys
+ return GetWatchPubKey(address, vchPubKeyOut);
+}
+
+CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
+{
+ assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
+ AssertLockHeld(cs_KeyStore);
+ bool fCompressed = m_storage.CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
+
+ CKey secret;
+
+ // Create new metadata
+ int64_t nCreationTime = GetTime();
+ CKeyMetadata metadata(nCreationTime);
+
+ // use HD key derivation if HD was enabled during wallet creation and a seed is present
+ if (IsHDEnabled()) {
+ DeriveNewChildKey(batch, metadata, secret, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
+ } else {
+ secret.MakeNewKey(fCompressed);
+ }
+
+ // Compressed public keys were introduced in version 0.6.0
+ if (fCompressed) {
+ m_storage.SetMinVersion(FEATURE_COMPRPUBKEY);
+ }
+
+ CPubKey pubkey = secret.GetPubKey();
+ assert(secret.VerifyPubKey(pubkey));
+
+ mapKeyMetadata[pubkey.GetID()] = metadata;
+ UpdateTimeFirstKey(nCreationTime);
+
+ if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
+ throw std::runtime_error(std::string(__func__) + ": AddKey failed");
+ }
+ return pubkey;
+}
+
+const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
+
+void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
+{
+ // for now we use a fixed keypath scheme of m/0'/0'/k
+ CKey seed; //seed (256bit)
+ CExtKey masterKey; //hd master key
+ CExtKey accountKey; //key at m/0'
+ CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
+ CExtKey childKey; //key at m/0'/0'/<n>'
+
+ // try to get the seed
+ if (!GetKey(hdChain.seed_id, seed))
+ throw std::runtime_error(std::string(__func__) + ": seed not found");
+
+ masterKey.SetSeed(seed.begin(), seed.size());
+
+ // derive m/0'
+ // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
+ masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
+
+ // derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
+ assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true);
+ accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
+
+ // derive child key at next index, skip keys already known to the wallet
+ do {
+ // always derive hardened keys
+ // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
+ // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
+ if (internal) {
+ chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
+ metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
+ metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
+ metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ hdChain.nInternalChainCounter++;
+ }
+ else {
+ chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
+ metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
+ metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
+ metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
+ hdChain.nExternalChainCounter++;
+ }
+ } while (HaveKey(childKey.key.GetPubKey().GetID()));
+ secret = childKey.key;
+ metadata.hd_seed_id = hdChain.seed_id;
+ CKeyID master_id = masterKey.key.GetPubKey().GetID();
+ std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
+ metadata.has_key_origin = true;
+ // update the chain model in the database
+ if (!batch.WriteHDChain(hdChain))
+ throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
+}
+
+void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
+{
+ LOCK(cs_KeyStore);
+ if (keypool.m_pre_split) {
+ set_pre_split_keypool.insert(nIndex);
+ } else if (keypool.fInternal) {
+ setInternalKeyPool.insert(nIndex);
+ } else {
+ setExternalKeyPool.insert(nIndex);
+ }
+ m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
+ m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
+
+ // If no metadata exists yet, create a default with the pool key's
+ // creation time. Note that this may be overwritten by actually
+ // stored metadata for that key later, which is fine.
+ CKeyID keyid = keypool.vchPubKey.GetID();
+ if (mapKeyMetadata.count(keyid) == 0)
+ mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
+}
+
+bool LegacyScriptPubKeyMan::CanGenerateKeys()
+{
+ // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD)
+ LOCK(cs_KeyStore);
+ return IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD);
+}
+
+CPubKey LegacyScriptPubKeyMan::GenerateNewSeed()
+{
+ assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ CKey key;
+ key.MakeNewKey(true);
+ return DeriveNewSeed(key);
+}
+
+CPubKey LegacyScriptPubKeyMan::DeriveNewSeed(const CKey& key)
+{
+ int64_t nCreationTime = GetTime();
+ CKeyMetadata metadata(nCreationTime);
+
+ // calculate the seed
+ CPubKey seed = key.GetPubKey();
+ assert(key.VerifyPubKey(seed));
+
+ // set the hd keypath to "s" -> Seed, refers the seed to itself
+ metadata.hdKeypath = "s";
+ metadata.has_key_origin = false;
+ metadata.hd_seed_id = seed.GetID();
+
+ {
+ LOCK(cs_KeyStore);
+
+ // mem store the metadata
+ mapKeyMetadata[seed.GetID()] = metadata;
+
+ // write the key&metadata to the database
+ if (!AddKeyPubKey(key, seed))
+ throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
+ }
+
+ return seed;
+}
+
+void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed)
+{
+ LOCK(cs_KeyStore);
+ // store the keyid (hash160) together with
+ // the child index counter in the database
+ // as a hdchain object
+ CHDChain newHdChain;
+ newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
+ newHdChain.seed_id = seed.GetID();
+ SetHDChain(newHdChain, false);
+ NotifyCanGetAddressesChanged();
+ WalletBatch batch(m_storage.GetDatabase());
+ m_storage.UnsetBlankWalletFlag(batch);
+}
+
+/**
+ * Mark old keypool keys as used,
+ * and generate all new keys
+ */
+bool LegacyScriptPubKeyMan::NewKeyPool()
+{
+ if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
+ {
+ LOCK(cs_KeyStore);
+ WalletBatch batch(m_storage.GetDatabase());
+
+ for (const int64_t nIndex : setInternalKeyPool) {
+ batch.ErasePool(nIndex);
+ }
+ setInternalKeyPool.clear();
+
+ for (const int64_t nIndex : setExternalKeyPool) {
+ batch.ErasePool(nIndex);
+ }
+ setExternalKeyPool.clear();
+
+ for (const int64_t nIndex : set_pre_split_keypool) {
+ batch.ErasePool(nIndex);
+ }
+ set_pre_split_keypool.clear();
+
+ m_pool_key_to_index.clear();
+
+ if (!TopUp()) {
+ return false;
+ }
+ WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n");
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
+{
+ if (!CanGenerateKeys()) {
+ return false;
+ }
+ {
+ LOCK(cs_KeyStore);
+
+ if (m_storage.IsLocked()) return false;
+
+ // Top up key pool
+ unsigned int nTargetSize;
+ if (kpSize > 0)
+ nTargetSize = kpSize;
+ else
+ nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
+
+ // count amount of available keys (internal, external)
+ // make sure the keypool of external and internal keys fits the user selected target (-keypool)
+ int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);
+ int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
+
+ if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))
+ {
+ // don't create extra internal keys
+ missingInternal = 0;
+ }
+ bool internal = false;
+ WalletBatch batch(m_storage.GetDatabase());
+ for (int64_t i = missingInternal + missingExternal; i--;)
+ {
+ if (i < missingInternal) {
+ internal = true;
+ }
+
+ CPubKey pubkey(GenerateNewKey(batch, internal));
+ AddKeypoolPubkeyWithDB(pubkey, internal, batch);
+ }
+ if (missingInternal + missingExternal > 0) {
+ WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
+ }
+ }
+ NotifyCanGetAddressesChanged();
+ return true;
+}
+
+void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
+{
+ LOCK(cs_KeyStore);
+ assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
+ int64_t index = ++m_max_keypool_index;
+ if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
+ throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed");
+ }
+ if (internal) {
+ setInternalKeyPool.insert(index);
+ } else {
+ setExternalKeyPool.insert(index);
+ }
+ m_pool_key_to_index[pubkey.GetID()] = index;
+}
+
+void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type)
+{
+ // Remove from key pool
+ WalletBatch batch(m_storage.GetDatabase());
+ batch.ErasePool(nIndex);
+ CPubKey pubkey;
+ bool have_pk = GetPubKey(m_index_to_reserved_key.at(nIndex), pubkey);
+ assert(have_pk);
+ LearnRelatedScripts(pubkey, type);
+ m_index_to_reserved_key.erase(nIndex);
+ WalletLogPrintf("keypool keep %d\n", nIndex);
+}
+
+void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, const CTxDestination&)
+{
+ // Return to key pool
+ {
+ LOCK(cs_KeyStore);
+ if (fInternal) {
+ setInternalKeyPool.insert(nIndex);
+ } else if (!set_pre_split_keypool.empty()) {
+ set_pre_split_keypool.insert(nIndex);
+ } else {
+ setExternalKeyPool.insert(nIndex);
+ }
+ CKeyID& pubkey_id = m_index_to_reserved_key.at(nIndex);
+ m_pool_key_to_index[pubkey_id] = nIndex;
+ m_index_to_reserved_key.erase(nIndex);
+ NotifyCanGetAddressesChanged();
+ }
+ WalletLogPrintf("keypool return %d\n", nIndex);
+}
+
+bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal)
+{
+ if (!CanGetAddresses(internal)) {
+ return false;
+ }
+
+ CKeyPool keypool;
+ {
+ LOCK(cs_KeyStore);
+ int64_t nIndex;
+ if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (m_storage.IsLocked()) return false;
+ WalletBatch batch(m_storage.GetDatabase());
+ result = GenerateNewKey(batch, internal);
+ return true;
+ }
+ KeepDestination(nIndex, type);
+ result = keypool.vchPubKey;
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
+{
+ nIndex = -1;
+ keypool.vchPubKey = CPubKey();
+ {
+ LOCK(cs_KeyStore);
+
+ bool fReturningInternal = fRequestedInternal;
+ fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ bool use_split_keypool = set_pre_split_keypool.empty();
+ std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
+
+ // Get the oldest key
+ if (setKeyPool.empty()) {
+ return false;
+ }
+
+ WalletBatch batch(m_storage.GetDatabase());
+
+ auto it = setKeyPool.begin();
+ nIndex = *it;
+ setKeyPool.erase(it);
+ if (!batch.ReadPool(nIndex, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read failed");
+ }
+ CPubKey pk;
+ if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
+ throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
+ }
+ // If the key was pre-split keypool, we don't care about what type it is
+ if (use_split_keypool && keypool.fInternal != fReturningInternal) {
+ throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
+ }
+ if (!keypool.vchPubKey.IsValid()) {
+ throw std::runtime_error(std::string(__func__) + ": keypool entry invalid");
+ }
+
+ assert(m_index_to_reserved_key.count(nIndex) == 0);
+ m_index_to_reserved_key[nIndex] = keypool.vchPubKey.GetID();
+ m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
+ WalletLogPrintf("keypool reserve %d\n", nIndex);
+ }
+ NotifyCanGetAddressesChanged();
+ return true;
+}
+
+void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type)
+{
+ if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {
+ CTxDestination witdest = WitnessV0KeyHash(key.GetID());
+ CScript witprog = GetScriptForDestination(witdest);
+ // Make sure the resulting program is solvable.
+ assert(IsSolvable(*this, witprog));
+ AddCScript(witprog);
+ }
+}
+
+void LegacyScriptPubKeyMan::LearnAllRelatedScripts(const CPubKey& key)
+{
+ // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types.
+ LearnRelatedScripts(key, OutputType::P2SH_SEGWIT);
+}
+
+void LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id)
+{
+ AssertLockHeld(cs_KeyStore);
+ bool internal = setInternalKeyPool.count(keypool_id);
+ if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id));
+ std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool);
+ auto it = setKeyPool->begin();
+
+ WalletBatch batch(m_storage.GetDatabase());
+ while (it != std::end(*setKeyPool)) {
+ const int64_t& index = *(it);
+ if (index > keypool_id) break; // set*KeyPool is ordered
+
+ CKeyPool keypool;
+ if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary
+ m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
+ }
+ LearnAllRelatedScripts(keypool.vchPubKey);
+ batch.ErasePool(index);
+ WalletLogPrintf("keypool index %d removed\n", index);
+ it = setKeyPool->erase(it);
+ }
+}
+
+std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider)
+{
+ std::vector<CScript> dummy;
+ FlatSigningProvider out;
+ InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out);
+ std::vector<CKeyID> ret;
+ for (const auto& entry : out.pubkeys) {
+ ret.push_back(entry.first);
+ }
+ return ret;
+}
+
+void LegacyScriptPubKeyMan::MarkPreSplitKeys()
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) {
+ int64_t index = *it;
+ CKeyPool keypool;
+ if (!batch.ReadPool(index, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": read keypool entry failed");
+ }
+ keypool.m_pre_split = true;
+ if (!batch.WritePool(index, keypool)) {
+ throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed");
+ }
+ set_pre_split_keypool.insert(index);
+ it = setExternalKeyPool.erase(it);
+ }
+}
+
+bool LegacyScriptPubKeyMan::AddCScript(const CScript& redeemScript)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ return AddCScriptWithDB(batch, redeemScript);
+}
+
+bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript)
+{
+ if (!FillableSigningProvider::AddCScript(redeemScript))
+ return false;
+ if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
+ m_storage.UnsetBlankWalletFlag(batch);
+ return true;
+ }
+ return false;
+}
+
+bool LegacyScriptPubKeyMan::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info)
+{
+ LOCK(cs_KeyStore);
+ std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint);
+ mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path;
+ mapKeyMetadata[pubkey.GetID()].has_key_origin = true;
+ mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path);
+ return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
+}
+
+bool LegacyScriptPubKeyMan::ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ for (const auto& entry : scripts) {
+ CScriptID id(entry);
+ if (HaveCScript(id)) {
+ WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry));
+ continue;
+ }
+ if (!AddCScriptWithDB(batch, entry)) {
+ return false;
+ }
+
+ if (timestamp > 0) {
+ m_script_metadata[CScriptID(entry)].nCreateTime = timestamp;
+ }
+ }
+ if (timestamp > 0) {
+ UpdateTimeFirstKey(timestamp);
+ }
+
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ for (const auto& entry : privkey_map) {
+ const CKey& key = entry.second;
+ CPubKey pubkey = key.GetPubKey();
+ const CKeyID& id = entry.first;
+ assert(key.VerifyPubKey(pubkey));
+ // Skip if we already have the key
+ if (HaveKey(id)) {
+ WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey));
+ continue;
+ }
+ mapKeyMetadata[id].nCreateTime = timestamp;
+ // If the private key is not present in the wallet, insert it.
+ if (!AddKeyPubKeyWithDB(batch, key, pubkey)) {
+ return false;
+ }
+ UpdateTimeFirstKey(timestamp);
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ for (const auto& entry : key_origins) {
+ AddKeyOriginWithDB(batch, entry.second.first, entry.second.second);
+ }
+ for (const CKeyID& id : ordered_pubkeys) {
+ auto entry = pubkey_map.find(id);
+ if (entry == pubkey_map.end()) {
+ continue;
+ }
+ const CPubKey& pubkey = entry->second;
+ CPubKey temp;
+ if (GetPubKey(id, temp)) {
+ // Already have pubkey, skipping
+ WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp));
+ continue;
+ }
+ if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) {
+ return false;
+ }
+ mapKeyMetadata[id].nCreateTime = timestamp;
+
+ // Add to keypool only works with pubkeys
+ if (add_keypool) {
+ AddKeypoolPubkeyWithDB(pubkey, internal, batch);
+ NotifyCanGetAddressesChanged();
+ }
+ }
+ return true;
+}
+
+bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp)
+{
+ WalletBatch batch(m_storage.GetDatabase());
+ for (const CScript& script : script_pub_keys) {
+ if (!have_solving_data || !IsMine(script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
+ if (!AddWatchOnlyWithDB(batch, script, timestamp)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
+{
+ LOCK(cs_KeyStore);
+ if (!m_storage.HasEncryptionKeys()) {
+ return FillableSigningProvider::GetKeys();
+ }
+ std::set<CKeyID> set_address;
+ for (const auto& mi : mapCryptedKeys) {
+ set_address.insert(mi.first);
+ }
+ return set_address;
+}
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
new file mode 100644
index 0000000000..7b1c023bc9
--- /dev/null
+++ b/src/wallet/scriptpubkeyman.h
@@ -0,0 +1,466 @@
+// Copyright (c) 2019-2020 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
+#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
+
+#include <script/signingprovider.h>
+#include <script/standard.h>
+#include <wallet/crypter.h>
+#include <wallet/ismine.h>
+#include <wallet/walletdb.h>
+#include <wallet/walletutil.h>
+
+#include <boost/signals2/signal.hpp>
+
+enum class OutputType;
+
+// Wallet storage things that ScriptPubKeyMans need in order to be able to store things to the wallet database.
+// It provides access to things that are part of the entire wallet and not specific to a ScriptPubKeyMan such as
+// wallet flags, wallet version, encryption keys, encryption status, and the database itself. This allows a
+// ScriptPubKeyMan to have callbacks into CWallet without causing a circular dependency.
+// WalletStorage should be the same for all ScriptPubKeyMans of a wallet.
+class WalletStorage
+{
+public:
+ virtual ~WalletStorage() = default;
+ virtual const std::string GetDisplayName() const = 0;
+ virtual WalletDatabase& GetDatabase() = 0;
+ virtual bool IsWalletFlagSet(uint64_t) const = 0;
+ virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
+ virtual bool CanSupportFeature(enum WalletFeature) const = 0;
+ virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0;
+ virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
+ virtual bool HasEncryptionKeys() const = 0;
+ virtual bool IsLocked() const = 0;
+};
+
+//! Default for -keypool
+static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000;
+
+std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider);
+
+/** A key from a CWallet's keypool
+ *
+ * The wallet holds one (for pre HD-split wallets) or several keypools. These
+ * are sets of keys that have not yet been used to provide addresses or receive
+ * change.
+ *
+ * The Bitcoin Core wallet was originally a collection of unrelated private
+ * keys with their associated addresses. If a non-HD wallet generated a
+ * key/address, gave that address out and then restored a backup from before
+ * that key's generation, then any funds sent to that address would be
+ * lost definitively.
+ *
+ * The keypool was implemented to avoid this scenario (commit: 10384941). The
+ * wallet would generate a set of keys (100 by default). When a new public key
+ * was required, either to give out as an address or to use in a change output,
+ * it would be drawn from the keypool. The keypool would then be topped up to
+ * maintain 100 keys. This ensured that as long as the wallet hadn't used more
+ * than 100 keys since the previous backup, all funds would be safe, since a
+ * restored wallet would be able to scan for all owned addresses.
+ *
+ * A keypool also allowed encrypted wallets to give out addresses without
+ * having to be decrypted to generate a new private key.
+ *
+ * With the introduction of HD wallets (commit: f1902510), the keypool
+ * essentially became an address look-ahead pool. Restoring old backups can no
+ * longer definitively lose funds as long as the addresses used were from the
+ * wallet's HD seed (since all private keys can be rederived from the seed).
+ * However, if many addresses were used since the backup, then the wallet may
+ * not know how far ahead in the HD chain to look for its addresses. The
+ * keypool is used to implement a 'gap limit'. The keypool maintains a set of
+ * keys (by default 1000) ahead of the last used key and scans for the
+ * addresses of those keys. This avoids the risk of not seeing transactions
+ * involving the wallet's addresses, or of re-using the same address.
+ * In the unlikely case where none of the addresses in the `gap limit` are
+ * used on-chain, the look-ahead will not be incremented to keep
+ * a constant size and addresses beyond this range will not be detected by an
+ * old backup. For this reason, it is not recommended to decrease keypool size
+ * lower than default value.
+ *
+ * The HD-split wallet feature added a second keypool (commit: 02592f4c). There
+ * is an external keypool (for addresses to hand out) and an internal keypool
+ * (for change addresses).
+ *
+ * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is
+ * stored as sets of indexes in the wallet (setInternalKeyPool,
+ * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the
+ * index (m_pool_key_to_index). The CKeyPool object is used to
+ * serialize/deserialize the pool data to/from the database.
+ */
+class CKeyPool
+{
+public:
+ //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB
+ int64_t nTime;
+ //! The public key
+ CPubKey vchPubKey;
+ //! Whether this keypool entry is in the internal keypool (for change outputs)
+ bool fInternal;
+ //! Whether this key was generated for a keypool before the wallet was upgraded to HD-split
+ bool m_pre_split;
+
+ CKeyPool();
+ CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action) {
+ int nVersion = s.GetVersion();
+ if (!(s.GetType() & SER_GETHASH))
+ READWRITE(nVersion);
+ READWRITE(nTime);
+ READWRITE(vchPubKey);
+ if (ser_action.ForRead()) {
+ try {
+ READWRITE(fInternal);
+ }
+ catch (std::ios_base::failure&) {
+ /* flag as external address if we can't read the internal boolean
+ (this will be the case for any wallet before the HD chain split version) */
+ fInternal = false;
+ }
+ try {
+ READWRITE(m_pre_split);
+ }
+ catch (std::ios_base::failure&) {
+ /* flag as postsplit address if we can't read the m_pre_split boolean
+ (this will be the case for any wallet that upgrades to HD chain split)*/
+ m_pre_split = false;
+ }
+ }
+ else {
+ READWRITE(fInternal);
+ READWRITE(m_pre_split);
+ }
+ }
+};
+
+/*
+ * A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet.
+ * It contains the scripts and keys related to the scriptPubKeys it manages.
+ * A ScriptPubKeyMan will be able to give out scriptPubKeys to be used, as well as marking
+ * when a scriptPubKey has been used. It also handles when and how to store a scriptPubKey
+ * and its related scripts and keys, including encryption.
+ */
+class ScriptPubKeyMan
+{
+protected:
+ WalletStorage& m_storage;
+
+public:
+ ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
+ virtual ~ScriptPubKeyMan() {};
+ virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; }
+ virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
+
+ //! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it.
+ virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; }
+ virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; }
+
+ virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { return false; }
+ virtual void KeepDestination(int64_t index, const OutputType& type) {}
+ virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {}
+
+ /** Fills internal address pool. Use within ScriptPubKeyMan implementations should be used sparingly and only
+ * when something from the address pool is removed, excluding GetNewDestination and GetReservedDestination.
+ * External wallet code is primarily responsible for topping up prior to fetching new addresses
+ */
+ virtual bool TopUp(unsigned int size = 0) { return false; }
+
+ //! Mark unused addresses as being used
+ virtual void MarkUnusedAddresses(const CScript& script) {}
+
+ /** Sets up the key generation stuff, i.e. generates new HD seeds and sets them as active.
+ * Returns false if already setup or setup fails, true if setup is successful
+ * Set force=true to make it re-setup if already setup, used for upgrades
+ */
+ virtual bool SetupGeneration(bool force = false) { return false; }
+
+ /* Returns true if HD is enabled */
+ virtual bool IsHDEnabled() const { return false; }
+
+ /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
+ virtual bool CanGetAddresses(bool internal = false) { return false; }
+
+ /** Upgrades the wallet to the specified version */
+ virtual bool Upgrade(int prev_version, std::string& error) { return false; }
+
+ virtual bool HavePrivateKeys() const { return false; }
+
+ //! The action to do when the DB needs rewrite
+ virtual void RewriteDB() {}
+
+ virtual int64_t GetOldestKeyPoolTime() { return GetTime(); }
+
+ virtual size_t KeypoolCountExternalKeys() { return 0; }
+ virtual unsigned int GetKeyPoolSize() const { return 0; }
+
+ virtual int64_t GetTimeFirstKey() const { return 0; }
+
+ virtual const CKeyMetadata* GetMetadata(const CTxDestination& dest) const { return nullptr; }
+
+ virtual std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const { return nullptr; }
+
+ /** Whether this ScriptPubKeyMan can provide a SigningProvider (via GetSigningProvider) that, combined with
+ * sigdata, can produce a valid signature.
+ */
+ virtual bool CanProvide(const CScript& script, SignatureData& sigdata) { return false; }
+
+ virtual uint256 GetID() const { return uint256(); }
+
+ /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
+ template<typename... Params>
+ void WalletLogPrintf(std::string fmt, Params... parameters) const {
+ LogPrintf(("%s " + fmt).c_str(), m_storage.GetDisplayName(), parameters...);
+ };
+
+ /** Watch-only address added */
+ boost::signals2::signal<void (bool fHaveWatchOnly)> NotifyWatchonlyChanged;
+
+ /** Keypool has new keys */
+ boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
+};
+
+class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
+{
+private:
+ //! keeps track of whether Unlock has run a thorough check before
+ bool fDecryptionThoroughlyChecked = false;
+
+ using WatchOnlySet = std::set<CScript>;
+ using WatchKeyMap = std::map<CKeyID, CPubKey>;
+
+ WalletBatch *encrypted_batch GUARDED_BY(cs_KeyStore) = nullptr;
+
+ using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
+
+ CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
+ WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
+ WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
+
+ int64_t nTimeFirstKey GUARDED_BY(cs_KeyStore) = 0;
+
+ bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
+ bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+
+ /**
+ * Private version of AddWatchOnly method which does not accept a
+ * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
+ * the watch key did not previously have a timestamp associated with it.
+ * Because this is an inherited virtual method, it is accessible despite
+ * being marked private, but it is marked private anyway to encourage use
+ * of the other AddWatchOnly which accepts a timestamp and sets
+ * nTimeFirstKey more intelligently for more efficient rescans.
+ */
+ bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ bool AddWatchOnlyInMem(const CScript &dest);
+ //! Adds a watch-only address to the store, and saves it to disk.
+ bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ //! Adds a key to the store, and saves it to disk.
+ bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch);
+
+ //! Adds a script to the store and saves it to disk
+ bool AddCScriptWithDB(WalletBatch& batch, const CScript& script);
+
+ /** Add a KeyOriginInfo to the wallet */
+ bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
+
+ /* the HD chain data model (external chain counters) */
+ CHDChain hdChain;
+
+ /* HD derive new child key (on internal or external chain) */
+ void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_KeyStore);
+ std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_KeyStore);
+ std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_KeyStore);
+ int64_t m_max_keypool_index GUARDED_BY(cs_KeyStore) = 0;
+ std::map<CKeyID, int64_t> m_pool_key_to_index;
+ // Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it
+ std::map<int64_t, CKeyID> m_index_to_reserved_key;
+
+ //! Fetches a key from the keypool
+ bool GetKeyFromPool(CPubKey &key, const OutputType type, bool internal = false);
+
+ /**
+ * Reserves a key from the keypool and sets nIndex to its index
+ *
+ * @param[out] nIndex the index of the key in keypool
+ * @param[out] keypool the keypool the key was drawn from, which could be the
+ * the pre-split pool if present, or the internal or external pool
+ * @param fRequestedInternal true if the caller would like the key drawn
+ * from the internal keypool, false if external is preferred
+ *
+ * @return true if succeeded, false if failed due to empty keypool
+ * @throws std::runtime_error if keypool read failed, key was invalid,
+ * was not found in the wallet, or was misclassified in the internal
+ * or external keypool
+ */
+ bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
+
+public:
+ using ScriptPubKeyMan::ScriptPubKeyMan;
+
+ bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override;
+ isminetype IsMine(const CScript& script) const override;
+
+ bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
+ bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
+
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override;
+ void KeepDestination(int64_t index, const OutputType& type) override;
+ void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override;
+
+ bool TopUp(unsigned int size = 0) override;
+
+ void MarkUnusedAddresses(const CScript& script) override;
+
+ //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
+ void UpgradeKeyMetadata();
+
+ bool IsHDEnabled() const override;
+
+ bool SetupGeneration(bool force = false) override;
+
+ bool Upgrade(int prev_version, std::string& error) override;
+
+ bool HavePrivateKeys() const override;
+
+ void RewriteDB() override;
+
+ int64_t GetOldestKeyPoolTime() override;
+ size_t KeypoolCountExternalKeys() override;
+ unsigned int GetKeyPoolSize() const override;
+
+ int64_t GetTimeFirstKey() const override;
+
+ const CKeyMetadata* GetMetadata(const CTxDestination& dest) const override;
+
+ bool CanGetAddresses(bool internal = false) override;
+
+ std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const override;
+
+ bool CanProvide(const CScript& script, SignatureData& sigdata) override;
+
+ uint256 GetID() const override;
+
+ // Map from Key ID to key metadata.
+ std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_KeyStore);
+
+ // Map from Script ID to key metadata (for watch-only keys).
+ std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_KeyStore);
+
+ //! Adds a key to the store, and saves it to disk.
+ bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
+ //! Adds a key to the store, without saving it to disk (used by LoadWallet)
+ bool LoadKey(const CKey& key, const CPubKey &pubkey);
+ //! Adds an encrypted key to the store, and saves it to disk.
+ bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+ //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
+ bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
+ void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ //! Adds a CScript to the store
+ bool LoadCScript(const CScript& redeemScript);
+ //! Load metadata (used by LoadWallet)
+ void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
+ void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
+ //! Generate a new key
+ CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ /* Set the HD chain model (chain child index counters) */
+ void SetHDChain(const CHDChain& chain, bool memonly);
+ const CHDChain& GetHDChain() const { return hdChain; }
+
+ //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
+ bool LoadWatchOnly(const CScript &dest);
+ //! Returns whether the watch-only script is in the wallet
+ bool HaveWatchOnly(const CScript &dest) const;
+ //! Returns whether there are any watch-only things in the wallet
+ bool HaveWatchOnly() const;
+ //! Remove a watch only script from the keystore
+ bool RemoveWatchOnly(const CScript &dest);
+ bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ //! Fetches a pubkey from mapWatchKeys if it exists there
+ bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const;
+
+ /* SigningProvider overrides */
+ bool HaveKey(const CKeyID &address) const override;
+ bool GetKey(const CKeyID &address, CKey& keyOut) const override;
+ bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
+ bool AddCScript(const CScript& redeemScript) override;
+ bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
+
+ //! Load a keypool entry
+ void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool);
+ bool NewKeyPool();
+ void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ bool ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+
+ /* Returns true if the wallet can generate new keys */
+ bool CanGenerateKeys();
+
+ /* Generates a new HD seed (will not be activated) */
+ CPubKey GenerateNewSeed();
+
+ /* Derives a new HD seed (will not be activated) */
+ CPubKey DeriveNewSeed(const CKey& key);
+
+ /* Set the current HD seed (will reset the chain child index counters)
+ Sets the seed's version based on the current wallet version (so the
+ caller must ensure the current wallet version is correct before calling
+ this function). */
+ void SetHDSeed(const CPubKey& key);
+
+ /**
+ * 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 FillableSigningProvider 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);
+
+ /**
+ * Marks all keys in the keypool up to and including reserve_key as used.
+ */
+ void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
+ const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
+
+ std::set<CKeyID> GetKeys() const override;
+};
+
+/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr */
+class LegacySigningProvider : public SigningProvider
+{
+private:
+ const LegacyScriptPubKeyMan& m_spk_man;
+public:
+ LegacySigningProvider(const LegacyScriptPubKeyMan& spk_man) : m_spk_man(spk_man) {}
+
+ bool GetCScript(const CScriptID &scriptid, CScript& script) const override { return m_spk_man.GetCScript(scriptid, script); }
+ bool HaveCScript(const CScriptID &scriptid) const override { return m_spk_man.HaveCScript(scriptid); }
+ bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const override { return m_spk_man.GetPubKey(address, pubkey); }
+ bool GetKey(const CKeyID &address, CKey& key) const override { return m_spk_man.GetKey(address, key); }
+ bool HaveKey(const CKeyID &address) const override { return m_spk_man.HaveKey(address); }
+ bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
+};
+
+#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 9e7f0ed773..d65a0e9075 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -2,13 +2,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <node/context.h>
#include <wallet/wallet.h>
#include <wallet/coinselection.h>
#include <wallet/coincontrol.h>
#include <amount.h>
#include <primitives/transaction.h>
#include <random.h>
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <wallet/test/wallet_test_fixture.h>
#include <boost/test/unit_test.hpp>
@@ -28,7 +29,8 @@ std::vector<std::unique_ptr<CWalletTx>> wtxn;
typedef std::set<CInputCoin> CoinSet;
static std::vector<COutput> vCoins;
-static auto testChain = interfaces::MakeChain();
+static NodeContext testNode;
+static auto testChain = interfaces::MakeChain(testNode);
static CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy());
static CAmount balance = 0;
@@ -53,7 +55,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set)
set.emplace(MakeTransactionRef(tx), nInput);
}
-static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
+static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
{
balance += nValue;
static int nextLockTime = 0;
@@ -61,21 +63,32 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
+ if (spendable) {
+ CTxDestination dest;
+ std::string error;
+ assert(wallet.GetNewDestination(OutputType::BECH32, "", dest, error));
+ tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
+ }
if (fIsFromMe) {
// IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
// so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
tx.vin.resize(1);
}
- std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx)));
+ std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)));
if (fIsFromMe)
{
wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
+ wtx->m_is_cache_empty = false;
}
COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
vCoins.push_back(output);
- testWallet.AddToWallet(*wtx.get());
+ wallet.AddToWallet(*wtx.get());
wtxn.emplace_back(std::move(wtx));
}
+static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
+{
+ add_coin(testWallet, nValue, nAge, fIsFromMe, nInput, spendable);
+}
static void empty_wallet(void)
{
@@ -123,6 +136,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
{
LOCK(testWallet.cs_wallet);
+ testWallet.SetupLegacyScriptPubKeyMan();
// Setup
std::vector<CInputCoin> utxo_pool;
@@ -250,17 +264,34 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
- // Make sure that we aren't using BnB when there are preset inputs
+ // Test fees subtracted from output:
empty_wallet();
- add_coin(5 * CENT);
- add_coin(3 * CENT);
- add_coin(2 * CENT);
- CCoinControl coin_control;
- coin_control.fAllowOtherInputs = true;
- coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
- BOOST_CHECK(testWallet.SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used));
- BOOST_CHECK(!bnb_used);
- BOOST_CHECK(!coin_selection_params_bnb.use_bnb);
+ add_coin(1 * CENT);
+ vCoins.at(0).nInputBytes = 40;
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ coin_selection_params_bnb.m_subtract_fee_outputs = true;
+ BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
+
+ // Make sure that can use BnB when there are preset inputs
+ empty_wallet();
+ {
+ std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock());
+ bool firstRun;
+ wallet->LoadWallet(firstRun);
+ wallet->SetupLegacyScriptPubKeyMan();
+ LOCK(wallet->cs_wallet);
+ add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true);
+ add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true);
+ add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true);
+ CCoinControl coin_control;
+ coin_control.fAllowOtherInputs = true;
+ coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
+ coin_selection_params_bnb.effective_fee = CFeeRate(0);
+ BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(bnb_used);
+ BOOST_CHECK(coin_selection_params_bnb.use_bnb);
+ }
}
BOOST_AUTO_TEST_CASE(knapsack_solver_test)
@@ -270,6 +301,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
bool bnb_used;
LOCK(testWallet.cs_wallet);
+ testWallet.SetupLegacyScriptPubKeyMan();
// test multiple times to allow for differences in the shuffle order
for (int i = 0; i < RUN_TESTS; i++)
@@ -549,6 +581,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
bool bnb_used;
LOCK(testWallet.cs_wallet);
+ testWallet.SetupLegacyScriptPubKeyMan();
empty_wallet();
@@ -567,6 +600,8 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
// Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value
BOOST_AUTO_TEST_CASE(SelectCoins_test)
{
+ testWallet.SetupLegacyScriptPubKeyMan();
+
// Random generator stuff
std::default_random_engine generator;
std::exponential_distribution<double> distribution (100);
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index c961456572..f4a4c9fa7c 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -7,7 +7,7 @@
#include <boost/test/unit_test.hpp>
#include <fs.h>
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <wallet/db.h>
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index 86ba0013fe..797a0d634f 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2018 The Bitcoin Core developers
+// Copyright (c) 2018-2019 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/init_test_fixture.h b/src/wallet/test/init_test_fixture.h
index e2b7075085..6ba7d66b7c 100644
--- a/src/wallet/test/init_test_fixture.h
+++ b/src/wallet/test/init_test_fixture.h
@@ -6,7 +6,8 @@
#define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H
#include <interfaces/chain.h>
-#include <test/setup_common.h>
+#include <node/context.h>
+#include <test/util/setup_common.h>
struct InitWalletDirTestingSetup: public BasicTestingSetup {
@@ -17,7 +18,8 @@ struct InitWalletDirTestingSetup: public BasicTestingSetup {
fs::path m_datadir;
fs::path m_cwd;
std::map<std::string, fs::path> m_walletdir_path_cases;
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
+ NodeContext m_node;
+ std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<interfaces::ChainClient> m_chain_client;
};
diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp
index 279542ffad..c228e06009 100644
--- a/src/wallet/test/init_tests.cpp
+++ b/src/wallet/test/init_tests.cpp
@@ -5,7 +5,8 @@
#include <boost/test/unit_test.hpp>
#include <noui.h>
-#include <test/setup_common.h>
+#include <test/util/logging.h>
+#include <test/util/setup_common.h>
#include <util/system.h>
#include <wallet/test/init_test_fixture.h>
@@ -34,28 +35,31 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom)
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist)
{
SetWalletDir(m_walletdir_path_cases["nonexistent"]);
- noui_suppress();
- bool result = m_chain_client->verify();
- noui_reconnect();
- BOOST_CHECK(result == false);
+ {
+ ASSERT_DEBUG_LOG("does not exist");
+ bool result = m_chain_client->verify();
+ BOOST_CHECK(result == false);
+ }
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory)
{
SetWalletDir(m_walletdir_path_cases["file"]);
- noui_suppress();
- bool result = m_chain_client->verify();
- noui_reconnect();
- BOOST_CHECK(result == false);
+ {
+ ASSERT_DEBUG_LOG("is not a directory");
+ bool result = m_chain_client->verify();
+ BOOST_CHECK(result == false);
+ }
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative)
{
SetWalletDir(m_walletdir_path_cases["relative"]);
- noui_suppress();
- bool result = m_chain_client->verify();
- noui_reconnect();
- BOOST_CHECK(result == false);
+ {
+ ASSERT_DEBUG_LOG("is a relative path");
+ bool result = m_chain_client->verify();
+ BOOST_CHECK(result == false);
+ }
}
BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing)
diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp
index 062fef7748..4c0e4dc653 100644
--- a/src/wallet/test/ismine_tests.cpp
+++ b/src/wallet/test/ismine_tests.cpp
@@ -3,9 +3,10 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <key.h>
+#include <node/context.h>
#include <script/script.h>
#include <script/standard.h>
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <wallet/ismine.h>
#include <wallet/wallet.h>
@@ -26,7 +27,8 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
CKey uncompressedKey;
uncompressedKey.MakeNewKey(false);
CPubKey uncompressedPubkey = uncompressedKey.GetPubKey();
- std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain();
+ NodeContext node;
+ std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node);
CScript scriptPubKey;
isminetype result;
@@ -34,363 +36,383 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK compressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(pubkeys[0]);
// Keystore does not have key
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has key
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2PK uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey);
// Keystore does not have key
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has key
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2PKH compressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0]));
// Keystore does not have key
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has key
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2PKH uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey));
// Keystore does not have key
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has key
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2SH
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript redeemScript = GetScriptForDestination(PKHash(pubkeys[0]));
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
// Keystore does not have redeemScript or key
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has redeemScript but no key
- BOOST_CHECK(keystore.AddCScript(redeemScript));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has redeemScript and key
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// (P2PKH inside) P2SH inside P2SH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript redeemscript_inner = GetScriptForDestination(PKHash(pubkeys[0]));
CScript redeemscript = GetScriptForDestination(ScriptHash(redeemscript_inner));
scriptPubKey = GetScriptForDestination(ScriptHash(redeemscript));
- BOOST_CHECK(keystore.AddCScript(redeemscript));
- BOOST_CHECK(keystore.AddCScript(redeemscript_inner));
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript_inner));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// (P2PKH inside) P2SH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript redeemscript = GetScriptForDestination(PKHash(pubkeys[0]));
CScript witnessscript = GetScriptForDestination(ScriptHash(redeemscript));
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
- BOOST_CHECK(keystore.AddCScript(witnessscript));
- BOOST_CHECK(keystore.AddCScript(redeemscript));
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemscript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// P2WPKH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript witnessscript = GetScriptForDestination(WitnessV0KeyHash(PKHash(pubkeys[0])));
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
- BOOST_CHECK(keystore.AddCScript(witnessscript));
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// (P2PKH inside) P2WSH inside P2WSH (invalid)
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript witnessscript_inner = GetScriptForDestination(PKHash(pubkeys[0]));
CScript witnessscript = GetScriptForDestination(WitnessV0ScriptHash(witnessscript_inner));
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessscript));
- BOOST_CHECK(keystore.AddCScript(witnessscript_inner));
- BOOST_CHECK(keystore.AddCScript(witnessscript));
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- BOOST_CHECK(keystore.AddKey(keys[0]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript_inner));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessscript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// P2WPKH compressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(PKHash(pubkeys[0])));
// Keystore implicitly has key and P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2WPKH uncompressed
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(PKHash(uncompressedPubkey)));
// Keystore has key, but no P2SH redeemScript
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has key and P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// scriptPubKey multisig
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
// Keystore does not have any keys
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has 1/2 keys
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has 2/2 keys
- BOOST_CHECK(keystore.AddKey(keys[1]));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has 2/2 keys and the script
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// P2SH multisig
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
- BOOST_CHECK(keystore.AddKey(keys[1]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
CScript redeemScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
// Keystore has no redeemScript
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has redeemScript
- BOOST_CHECK(keystore.AddCScript(redeemScript));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2WSH multisig with compressed keys
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
- BOOST_CHECK(keystore.AddKey(keys[1]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]});
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
// Keystore has keys, but no witnessScript or P2SH redeemScript
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has keys and witnessScript, but no P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(witnessScript));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has keys, witnessScript, P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// P2WSH multisig with uncompressed key
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(uncompressedKey));
- BOOST_CHECK(keystore.AddKey(keys[1]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
CScript witnessScript = GetScriptForMultisig(2, {uncompressedPubkey, pubkeys[1]});
scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
// Keystore has keys, but no witnessScript or P2SH redeemScript
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has keys and witnessScript, but no P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(witnessScript));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has keys, witnessScript, P2SH redeemScript
- BOOST_CHECK(keystore.AddCScript(scriptPubKey));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(scriptPubKey));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// P2WSH multisig wrapped in P2SH
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
CScript witnessScript = GetScriptForMultisig(2, {pubkeys[0], pubkeys[1]});
CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript));
scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript));
// Keystore has no witnessScript, P2SH redeemScript, or keys
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has witnessScript and P2SH redeemScript, but no keys
- BOOST_CHECK(keystore.AddCScript(redeemScript));
- BOOST_CHECK(keystore.AddCScript(witnessScript));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(redeemScript));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddCScript(witnessScript));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
// Keystore has keys, witnessScript, P2SH redeemScript
- BOOST_CHECK(keystore.AddKey(keys[0]));
- BOOST_CHECK(keystore.AddKey(keys[1]));
- result = IsMine(keystore, scriptPubKey);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[1]));
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_SPENDABLE);
}
// OP_RETURN
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
scriptPubKey.clear();
scriptPubKey << OP_RETURN << ToByteVector(pubkeys[0]);
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// witness unspendable
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
scriptPubKey.clear();
scriptPubKey << OP_0 << ToByteVector(ParseHex("aabb"));
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// witness unknown
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
scriptPubKey.clear();
scriptPubKey << OP_16 << ToByteVector(ParseHex("aabb"));
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
// Nonstandard
{
CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(keystore.cs_wallet);
- BOOST_CHECK(keystore.AddKey(keys[0]));
+ keystore.SetupLegacyScriptPubKeyMan();
+ LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
+ BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
scriptPubKey.clear();
scriptPubKey << OP_9 << OP_ADD << OP_11 << OP_EQUAL;
- result = IsMine(keystore, scriptPubKey);
+ result = keystore.GetLegacyScriptPubKeyMan()->IsMine(scriptPubKey);
BOOST_CHECK_EQUAL(result, ISMINE_NO);
}
}
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index 0400f1207c..f923de6178 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -9,14 +9,15 @@
#include <wallet/wallet.h>
#include <boost/test/unit_test.hpp>
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <wallet/test/wallet_test_fixture.h>
BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup)
BOOST_AUTO_TEST_CASE(psbt_updater_test)
{
- LOCK(m_wallet.cs_wallet);
+ auto spk_man = m_wallet.GetOrCreateLegacyScriptPubKeyMan();
+ LOCK2(m_wallet.cs_wallet, spk_man->cs_KeyStore);
// Create prevtxs and add to wallet
CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
@@ -35,23 +36,23 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
CScript rs1;
CDataStream s_rs1(ParseHex("475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae"), SER_NETWORK, PROTOCOL_VERSION);
s_rs1 >> rs1;
- m_wallet.AddCScript(rs1);
+ spk_man->AddCScript(rs1);
CScript rs2;
CDataStream s_rs2(ParseHex("2200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903"), SER_NETWORK, PROTOCOL_VERSION);
s_rs2 >> rs2;
- m_wallet.AddCScript(rs2);
+ spk_man->AddCScript(rs2);
CScript ws1;
CDataStream s_ws1(ParseHex("47522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae"), SER_NETWORK, PROTOCOL_VERSION);
s_ws1 >> ws1;
- m_wallet.AddCScript(ws1);
+ spk_man->AddCScript(ws1);
// Add hd seed
CKey key = DecodeSecret("5KSSJQ7UNfFGwVgpCZDSHm5rVNhMFcFtvWM3zQ8mW4qNDEN7LFd"); // Mainnet and uncompressed form of cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T
- CPubKey master_pub_key = m_wallet.DeriveNewSeed(key);
- m_wallet.SetHDSeed(master_pub_key);
- m_wallet.NewKeyPool();
+ CPubKey master_pub_key = spk_man->DeriveNewSeed(key);
+ spk_man->SetHDSeed(master_pub_key);
+ spk_man->NewKeyPool();
// Call FillPSBT
PartiallySignedTransaction psbtx;
@@ -67,6 +68,15 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
ssTx << psbtx;
std::string final_hex = HexStr(ssTx.begin(), ssTx.end());
BOOST_CHECK_EQUAL(final_hex, "70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88701042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000");
+
+ // Mutate the transaction so that one of the inputs is invalid
+ psbtx.tx->vin[0].prevout.n = 2;
+
+ // Try to sign the mutated input
+ SignatureData sigdata;
+ psbtx.inputs[0].FillSignatureData(sigdata);
+ const std::unique_ptr<SigningProvider> provider = m_wallet.GetSigningProvider(ws1, sigdata);
+ BOOST_CHECK(!SignPSBTInput(*provider, psbtx, 0, SIGHASH_ALL));
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)
diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp
index 2f41813234..97f8c94fa6 100644
--- a/src/wallet/test/wallet_crypto_tests.cpp
+++ b/src/wallet/test/wallet_crypto_tests.cpp
@@ -2,7 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <util/strencodings.h>
#include <wallet/crypter.h>
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index c1dbecdf8c..4e4129fb2c 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -5,10 +5,11 @@
#ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
#define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
+#include <node/context.h>
#include <wallet/wallet.h>
#include <memory>
@@ -18,7 +19,8 @@
struct WalletTestingSetup: public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
+ NodeContext m_node;
+ std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<interfaces::ChainClient> m_chain_client = interfaces::MakeWalletClient(*m_chain, {});
CWallet m_wallet;
};
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index a2b2a7b227..a487e9e2e0 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -9,9 +9,10 @@
#include <vector>
#include <interfaces/chain.h>
+#include <node/context.h>
#include <policy/policy.h>
#include <rpc/server.h>
-#include <test/setup_common.h>
+#include <test/util/setup_common.h>
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/test/wallet_test_fixture.h>
@@ -27,8 +28,9 @@ BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
static void AddKey(CWallet& wallet, const CKey& key)
{
- LOCK(wallet.cs_wallet);
- wallet.AddKeyPubKey(key, key.GetPubKey());
+ auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
+ LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
+ spk_man->AddKeyPubKey(key, key.GetPubKey());
}
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
@@ -39,13 +41,18 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = ::ChainActive().Tip();
- auto chain = interfaces::MakeChain();
+ NodeContext node;
+ auto chain = interfaces::MakeChain(node);
auto locked_chain = chain->lock();
LockAssertion lock(::cs_main);
// Verify ScanForWalletTransactions accommodates a null start block.
{
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
+ }
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -61,6 +68,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// and new block files.
{
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
+ }
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -80,6 +91,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// file.
{
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
+ }
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -98,6 +113,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions scans no blocks.
{
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
+ }
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@@ -118,7 +137,8 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = ::ChainActive().Tip();
- auto chain = interfaces::MakeChain();
+ NodeContext node;
+ auto chain = interfaces::MakeChain(node);
auto locked_chain = chain->lock();
LockAssertion lock(::cs_main);
@@ -131,6 +151,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
// after.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ wallet->SetupLegacyScriptPubKeyMan();
AddWallet(wallet);
UniValue keys;
keys.setArray();
@@ -185,7 +206,8 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
SetMockTime(KEY_TIME);
m_coinbase_txns.emplace_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
- auto chain = interfaces::MakeChain();
+ NodeContext node;
+ auto chain = interfaces::MakeChain(node);
auto locked_chain = chain->lock();
LockAssertion lock(::cs_main);
@@ -194,9 +216,10 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
- LOCK(wallet->cs_wallet);
- wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
- wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
+ auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
+ LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
+ spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
+ spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
JSONRPCRequest request;
request.params.setArray();
@@ -210,6 +233,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// were scanned, and no prior blocks were scanned.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ wallet->SetupLegacyScriptPubKeyMan();
JSONRPCRequest request;
request.params.setArray();
@@ -239,26 +263,30 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
- auto chain = interfaces::MakeChain();
+ NodeContext node;
+ auto chain = interfaces::MakeChain(node);
CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
CWalletTx wtx(&wallet, m_coinbase_txns.back());
auto locked_chain = chain->lock();
LockAssertion lock(::cs_main);
- LOCK(wallet.cs_wallet);
+ LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
+ wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
- wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0);
+ wtx.m_confirm = confirm;
// Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0.
- BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0);
+ BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0);
// Invalidate the cached value, add the key, and make sure a new immature
// credit amount is calculated.
wtx.MarkDirty();
- wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
- BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN);
+ BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()));
+ BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN);
}
static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
@@ -289,7 +317,8 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
wallet.AddToWallet(wtx);
}
if (block) {
- wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block->nHeight, block->GetBlockHash(), 0);
+ wtx.m_confirm = confirm;
}
wallet.AddToWallet(wtx);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
@@ -327,9 +356,10 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
CTxDestination dest = PKHash();
LOCK(m_wallet.cs_wallet);
- m_wallet.AddDestData(dest, "misc", "val_misc");
- m_wallet.AddDestData(dest, "rr0", "val_rr0");
- m_wallet.AddDestData(dest, "rr1", "val_rr1");
+ WalletBatch batch{m_wallet.GetDatabase()};
+ m_wallet.AddDestData(batch, dest, "misc", "val_misc");
+ m_wallet.AddDestData(batch, dest, "rr0", "val_rr0");
+ m_wallet.AddDestData(batch, dest, "rr1", "val_rr1");
auto values = m_wallet.GetDestValues("rr");
BOOST_CHECK_EQUAL(values.size(), 2U);
@@ -337,37 +367,37 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
BOOST_CHECK_EQUAL(values[1], "val_rr1");
}
-// Test some watch-only wallet methods by the procedure of loading (LoadWatchOnly),
+// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of loading (LoadWatchOnly),
// checking (HaveWatchOnly), getting (GetWatchPubKey) and removing (RemoveWatchOnly) a
// given PubKey, resp. its corresponding P2PK Script. Results of the the impact on
// the address -> PubKey map is dependent on whether the PubKey is a point on the curve
-static void TestWatchOnlyPubKey(CWallet& wallet, const CPubKey& add_pubkey)
+static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan* spk_man, const CPubKey& add_pubkey)
{
CScript p2pk = GetScriptForRawPubKey(add_pubkey);
CKeyID add_address = add_pubkey.GetID();
CPubKey found_pubkey;
- LOCK(wallet.cs_wallet);
+ LOCK(spk_man->cs_KeyStore);
// all Scripts (i.e. also all PubKeys) are added to the general watch-only set
- BOOST_CHECK(!wallet.HaveWatchOnly(p2pk));
- wallet.LoadWatchOnly(p2pk);
- BOOST_CHECK(wallet.HaveWatchOnly(p2pk));
+ BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
+ spk_man->LoadWatchOnly(p2pk);
+ BOOST_CHECK(spk_man->HaveWatchOnly(p2pk));
// only PubKeys on the curve shall be added to the watch-only address -> PubKey map
bool is_pubkey_fully_valid = add_pubkey.IsFullyValid();
if (is_pubkey_fully_valid) {
- BOOST_CHECK(wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(spk_man->GetWatchPubKey(add_address, found_pubkey));
BOOST_CHECK(found_pubkey == add_pubkey);
} else {
- BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
BOOST_CHECK(found_pubkey == CPubKey()); // passed key is unchanged
}
- wallet.RemoveWatchOnly(p2pk);
- BOOST_CHECK(!wallet.HaveWatchOnly(p2pk));
+ spk_man->RemoveWatchOnly(p2pk);
+ BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
if (is_pubkey_fully_valid) {
- BOOST_CHECK(!wallet.GetWatchPubKey(add_address, found_pubkey));
+ BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
BOOST_CHECK(found_pubkey == add_pubkey); // passed key is unchanged
}
}
@@ -382,37 +412,38 @@ static void PollutePubKey(CPubKey& pubkey)
assert(pubkey.IsValid());
}
-// Test watch-only wallet logic for PubKeys
+// Test watch-only logic for PubKeys
BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys)
{
CKey key;
CPubKey pubkey;
+ LegacyScriptPubKeyMan* spk_man = m_wallet.GetOrCreateLegacyScriptPubKeyMan();
- BOOST_CHECK(!m_wallet.HaveWatchOnly());
+ BOOST_CHECK(!spk_man->HaveWatchOnly());
// uncompressed valid PubKey
key.MakeNewKey(false);
pubkey = key.GetPubKey();
assert(!pubkey.IsCompressed());
- TestWatchOnlyPubKey(m_wallet, pubkey);
+ TestWatchOnlyPubKey(spk_man, pubkey);
// uncompressed cryptographically invalid PubKey
PollutePubKey(pubkey);
- TestWatchOnlyPubKey(m_wallet, pubkey);
+ TestWatchOnlyPubKey(spk_man, pubkey);
// compressed valid PubKey
key.MakeNewKey(true);
pubkey = key.GetPubKey();
assert(pubkey.IsCompressed());
- TestWatchOnlyPubKey(m_wallet, pubkey);
+ TestWatchOnlyPubKey(spk_man, pubkey);
// compressed cryptographically invalid PubKey
PollutePubKey(pubkey);
- TestWatchOnlyPubKey(m_wallet, pubkey);
+ TestWatchOnlyPubKey(spk_man, pubkey);
// invalid empty PubKey
pubkey = CPubKey();
- TestWatchOnlyPubKey(m_wallet, pubkey);
+ TestWatchOnlyPubKey(spk_man, pubkey);
}
class ListCoinsTestingSetup : public TestChain100Setup
@@ -422,6 +453,10 @@ public:
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock());
+ {
+ LOCK(wallet->cs_wallet);
+ wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash());
+ }
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
@@ -460,13 +495,16 @@ public:
LOCK(cs_main);
LOCK(wallet->cs_wallet);
+ wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
- it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1);
+ it->second.m_confirm = confirm;
return it->second;
}
- std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain();
+ NodeContext m_node;
+ std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node);
std::unique_ptr<CWallet> wallet;
};
@@ -538,8 +576,10 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
- auto chain = interfaces::MakeChain();
+ NodeContext node;
+ auto chain = interfaces::MakeChain(node);
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy());
+ wallet->SetupLegacyScriptPubKeyMan();
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 159d4f78c6..4a38571dfc 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -45,8 +45,9 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
-static CCriticalSection cs_wallets;
+static RecursiveMutex cs_wallets;
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
+static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
bool AddWallet(const std::shared_ptr<CWallet>& wallet)
{
@@ -55,6 +56,7 @@ bool AddWallet(const std::shared_ptr<CWallet>& wallet)
std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i != vpwallets.end()) return false;
vpwallets.push_back(wallet);
+ wallet->ConnectScriptPubKeyManNotifiers();
return true;
}
@@ -89,6 +91,13 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
return nullptr;
}
+std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet)
+{
+ LOCK(cs_wallets);
+ auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(), std::move(load_wallet));
+ return interfaces::MakeHandler([it] { LOCK(cs_wallets); g_load_wallet_fns.erase(it); });
+}
+
static Mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
static std::set<std::string> g_unloading_wallet_set;
@@ -210,9 +219,15 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
}
// Set a seed for the wallet
- CPubKey master_pub_key = wallet->GenerateNewSeed();
- wallet->SetHDSeed(master_pub_key);
- wallet->NewKeyPool();
+ {
+ LOCK(wallet->cs_wallet);
+ for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
+ if (!spk_man->SetupGeneration()) {
+ error = "Unable to generate initial keys";
+ return WalletCreationStatus::CREATION_FAILED;
+ }
+ }
+ }
// Relock the wallet
wallet->Lock();
@@ -224,9 +239,7 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString&
return WalletCreationStatus::SUCCESS;
}
-const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
-
-const uint256 CWalletTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
+const uint256 CWalletTx::ABANDON_HASH(UINT256_ONE());
/** @defgroup mapWallet
*
@@ -238,18 +251,6 @@ std::string COutput::ToString() const
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
-std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider)
-{
- std::vector<CScript> dummy;
- FlatSigningProvider out;
- InferDescriptor(spk, provider)->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out);
- std::vector<CKeyID> ret;
- for (const auto& entry : out.pubkeys) {
- ret.push_back(entry.first);
- }
- return ret;
-}
-
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
LOCK(cs_wallet);
@@ -259,354 +260,19 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
return &(it->second);
}
-CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
-{
- assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
- assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
- AssertLockHeld(cs_wallet);
- bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
-
- CKey secret;
-
- // Create new metadata
- int64_t nCreationTime = GetTime();
- CKeyMetadata metadata(nCreationTime);
-
- // use HD key derivation if HD was enabled during wallet creation and a seed is present
- if (IsHDEnabled()) {
- DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
- } else {
- secret.MakeNewKey(fCompressed);
- }
-
- // Compressed public keys were introduced in version 0.6.0
- if (fCompressed) {
- SetMinVersion(FEATURE_COMPRPUBKEY);
- }
-
- CPubKey pubkey = secret.GetPubKey();
- assert(secret.VerifyPubKey(pubkey));
-
- mapKeyMetadata[pubkey.GetID()] = metadata;
- UpdateTimeFirstKey(nCreationTime);
-
- if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
- throw std::runtime_error(std::string(__func__) + ": AddKey failed");
- }
- return pubkey;
-}
-
-void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
-{
- // for now we use a fixed keypath scheme of m/0'/0'/k
- CKey seed; //seed (256bit)
- CExtKey masterKey; //hd master key
- CExtKey accountKey; //key at m/0'
- CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
- CExtKey childKey; //key at m/0'/0'/<n>'
-
- // try to get the seed
- if (!GetKey(hdChain.seed_id, seed))
- throw std::runtime_error(std::string(__func__) + ": seed not found");
-
- masterKey.SetSeed(seed.begin(), seed.size());
-
- // derive m/0'
- // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
- masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
-
- // derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
- assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
- accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
-
- // derive child key at next index, skip keys already known to the wallet
- do {
- // always derive hardened keys
- // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
- // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
- if (internal) {
- chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
- metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- hdChain.nInternalChainCounter++;
- }
- else {
- chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
- metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
- metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
- hdChain.nExternalChainCounter++;
- }
- } while (HaveKey(childKey.key.GetPubKey().GetID()));
- secret = childKey.key;
- metadata.hd_seed_id = hdChain.seed_id;
- CKeyID master_id = masterKey.key.GetPubKey().GetID();
- std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
- metadata.has_key_origin = true;
- // update the chain model in the database
- if (!batch.WriteHDChain(hdChain))
- throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
-}
-
-bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey)
-{
- AssertLockHeld(cs_wallet);
-
- // Make sure we aren't adding private keys to private key disabled wallets
- assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
-
- // FillableSigningProvider has no concept of wallet databases, but calls AddCryptedKey
- // which is overridden below. To avoid flushes, the database handle is
- // tunneled through to it.
- bool needsDB = !encrypted_batch;
- if (needsDB) {
- encrypted_batch = &batch;
- }
- if (!AddKeyPubKeyInner(secret, pubkey)) {
- if (needsDB) encrypted_batch = nullptr;
- return false;
- }
- if (needsDB) encrypted_batch = nullptr;
-
- // check if we need to remove from watch-only
- CScript script;
- script = GetScriptForDestination(PKHash(pubkey));
- if (HaveWatchOnly(script)) {
- RemoveWatchOnly(script);
- }
- script = GetScriptForRawPubKey(pubkey);
- if (HaveWatchOnly(script)) {
- RemoveWatchOnly(script);
- }
-
- if (!IsCrypted()) {
- return batch.WriteKey(pubkey,
- secret.GetPrivKey(),
- mapKeyMetadata[pubkey.GetID()]);
- }
- UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
- return true;
-}
-
-bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
-{
- WalletBatch batch(*database);
- return CWallet::AddKeyPubKeyWithDB(batch, secret, pubkey);
-}
-
-bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
- const std::vector<unsigned char> &vchCryptedSecret)
-{
- if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret))
- return false;
- {
- LOCK(cs_wallet);
- if (encrypted_batch)
- return encrypted_batch->WriteCryptedKey(vchPubKey,
- vchCryptedSecret,
- mapKeyMetadata[vchPubKey.GetID()]);
- else
- return WalletBatch(*database).WriteCryptedKey(vchPubKey,
- vchCryptedSecret,
- mapKeyMetadata[vchPubKey.GetID()]);
- }
-}
-
-void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta)
-{
- AssertLockHeld(cs_wallet);
- UpdateTimeFirstKey(meta.nCreateTime);
- mapKeyMetadata[keyID] = meta;
-}
-
-void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta)
-{
- AssertLockHeld(cs_wallet);
- UpdateTimeFirstKey(meta.nCreateTime);
- m_script_metadata[script_id] = meta;
-}
-
void CWallet::UpgradeKeyMetadata()
{
- AssertLockHeld(cs_wallet);
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
return;
}
- std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database);
- for (auto& meta_pair : mapKeyMetadata) {
- CKeyMetadata& meta = meta_pair.second;
- if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { // If the hdKeypath is "s", that's the seed and it doesn't have a key origin
- CKey key;
- GetKey(meta.hd_seed_id, key);
- CExtKey masterKey;
- masterKey.SetSeed(key.begin(), key.size());
- // Add to map
- CKeyID master_id = masterKey.key.GetPubKey().GetID();
- std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint);
- if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) {
- throw std::runtime_error("Invalid stored hdKeypath");
- }
- meta.has_key_origin = true;
- if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
- meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
- }
-
- // Write meta to wallet
- CPubKey pubkey;
- if (GetPubKey(meta_pair.first, pubkey)) {
- batch->WriteKeyMetadata(meta, pubkey, true);
- }
- }
- }
- batch.reset(); //write before setting the flag
- SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
-}
-
-bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
-{
- return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
-}
-
-/**
- * Update wallet first key creation time. This should be called whenever keys
- * are added to the wallet, with the oldest key creation time.
- */
-void CWallet::UpdateTimeFirstKey(int64_t nCreateTime)
-{
- AssertLockHeld(cs_wallet);
- if (nCreateTime <= 1) {
- // Cannot determine birthday information, so set the wallet birthday to
- // the beginning of time.
- nTimeFirstKey = 1;
- } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
- nTimeFirstKey = nCreateTime;
- }
-}
-
-bool CWallet::AddCScript(const CScript& redeemScript)
-{
- WalletBatch batch(*database);
- return AddCScriptWithDB(batch, redeemScript);
-}
-
-bool CWallet::AddCScriptWithDB(WalletBatch& batch, const CScript& redeemScript)
-{
- if (!FillableSigningProvider::AddCScript(redeemScript))
- return false;
- if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
- UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
- return true;
- }
- return false;
-}
-
-bool CWallet::LoadCScript(const CScript& redeemScript)
-{
- /* A sanity check was added in pull #3843 to avoid adding redeemScripts
- * that never can be redeemed. However, old wallets may still contain
- * these. Do not add them to the wallet and warn. */
- if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
- {
- std::string strAddr = EncodeDestination(ScriptHash(redeemScript));
- WalletLogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
- return true;
- }
-
- return FillableSigningProvider::AddCScript(redeemScript);
-}
-
-static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
-{
- std::vector<std::vector<unsigned char>> solutions;
- return Solver(dest, solutions) == TX_PUBKEY &&
- (pubKeyOut = CPubKey(solutions[0])).IsFullyValid();
-}
-
-bool CWallet::AddWatchOnlyInMem(const CScript &dest)
-{
- LOCK(cs_KeyStore);
- setWatchOnly.insert(dest);
- CPubKey pubKey;
- if (ExtractPubKey(dest, pubKey)) {
- mapWatchKeys[pubKey.GetID()] = pubKey;
- ImplicitlyLearnRelatedKeyScripts(pubKey);
- }
- return true;
-}
-
-bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest)
-{
- if (!AddWatchOnlyInMem(dest))
- return false;
- const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
- UpdateTimeFirstKey(meta.nCreateTime);
- NotifyWatchonlyChanged(true);
- if (batch.WriteWatchOnly(dest, meta)) {
- UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
- return true;
- }
- return false;
-}
-
-bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time)
-{
- m_script_metadata[CScriptID(dest)].nCreateTime = create_time;
- return AddWatchOnlyWithDB(batch, dest);
-}
-
-bool CWallet::AddWatchOnly(const CScript& dest)
-{
- WalletBatch batch(*database);
- return AddWatchOnlyWithDB(batch, dest);
-}
-
-bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
-{
- m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
- return AddWatchOnly(dest);
-}
-
-bool CWallet::RemoveWatchOnly(const CScript &dest)
-{
- AssertLockHeld(cs_wallet);
- {
- LOCK(cs_KeyStore);
- setWatchOnly.erase(dest);
- CPubKey pubKey;
- if (ExtractPubKey(dest, pubKey)) {
- mapWatchKeys.erase(pubKey.GetID());
- }
- // Related CScripts are not removed; having superfluous scripts around is
- // harmless (see comment in ImplicitlyLearnRelatedKeyScripts).
+ auto spk_man = GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ return;
}
- if (!HaveWatchOnly())
- NotifyWatchonlyChanged(false);
- if (!WalletBatch(*database).EraseWatchOnly(dest))
- return false;
-
- return true;
-}
-
-bool CWallet::LoadWatchOnly(const CScript &dest)
-{
- return AddWatchOnlyInMem(dest);
-}
-
-bool CWallet::HaveWatchOnly(const CScript &dest) const
-{
- LOCK(cs_KeyStore);
- return setWatchOnly.count(dest) > 0;
-}
-
-bool CWallet::HaveWatchOnly() const
-{
- LOCK(cs_KeyStore);
- return (!setWatchOnly.empty());
+ spk_man->UpgradeKeyMetadata();
+ SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys)
@@ -798,7 +464,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran
* Outpoint is spent if any non-conflicted transaction
* spends it:
*/
-bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const
+bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{
const COutPoint outpoint(hash, n);
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
@@ -809,7 +475,7 @@ bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
- int depth = mit->second.GetDepthInMainChain(locked_chain);
+ int depth = mit->second.GetDepthInMainChain();
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
return true; // Spent
}
@@ -878,8 +544,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
- assert(!encrypted_batch);
- encrypted_batch = new WalletBatch(*database);
+ WalletBatch* encrypted_batch = new WalletBatch(*database);
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
@@ -887,14 +552,16 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
}
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
- if (!EncryptKeys(_vMasterKey))
- {
- encrypted_batch->TxnAbort();
- delete encrypted_batch;
- encrypted_batch = nullptr;
- // We now probably have half of our keys encrypted in memory, and half not...
- // die and let the user reload the unencrypted wallet.
- assert(false);
+ for (const auto& spk_man_pair : m_spk_managers) {
+ auto spk_man = spk_man_pair.second.get();
+ if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
+ encrypted_batch->TxnAbort();
+ delete encrypted_batch;
+ encrypted_batch = nullptr;
+ // We now probably have half of our keys encrypted in memory, and half not...
+ // die and let the user reload the unencrypted wallet.
+ assert(false);
+ }
}
// Encryption was introduced in version 0.4.0
@@ -915,11 +582,13 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
Unlock(strWalletPassphrase);
// if we are using HD, replace the HD seed with a new one
- if (IsHDEnabled()) {
- SetHDSeed(GenerateNewSeed());
+ if (auto spk_man = GetLegacyScriptPubKeyMan()) {
+ if (spk_man->IsHDEnabled()) {
+ if (!spk_man->SetupGeneration(true)) {
+ return false;
+ }
+ }
}
-
- NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
@@ -1044,35 +713,53 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
return success;
}
-void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool used)
+void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations)
{
+ AssertLockHeld(cs_wallet);
const CWalletTx* srctx = GetWalletTx(hash);
if (!srctx) return;
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
- if (::IsMine(*this, dst)) {
- LOCK(cs_wallet);
+ if (IsMine(dst)) {
if (used && !GetDestData(dst, "used", nullptr)) {
- AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null)
+ if (AddDestData(batch, dst, "used", "p")) { // p for "present", opposite of absent (null)
+ tx_destinations.insert(dst);
+ }
} else if (!used && GetDestData(dst, "used", nullptr)) {
- EraseDestData(dst, "used");
+ EraseDestData(batch, dst, "used");
}
}
}
}
-bool CWallet::IsUsedDestination(const CTxDestination& dst) const
-{
- LOCK(cs_wallet);
- return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr);
-}
-
bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const
{
+ AssertLockHeld(cs_wallet);
CTxDestination dst;
const CWalletTx* srctx = GetWalletTx(hash);
- return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst);
+ if (srctx) {
+ assert(srctx->tx->vout.size() > n);
+ LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
+ // When descriptor wallets arrive, these additional checks are
+ // likely superfluous and can be optimized out
+ assert(spk_man != nullptr);
+ for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
+ WitnessV0KeyHash wpkh_dest(keyid);
+ if (GetDestData(wpkh_dest, "used", nullptr)) {
+ return true;
+ }
+ ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest));
+ if (GetDestData(sh_wpkh_dest, "used", nullptr)) {
+ return true;
+ }
+ PKHash pkh_dest(keyid);
+ if (GetDestData(pkh_dest, "used", nullptr)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
@@ -1085,10 +772,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// Mark used destinations
+ std::set<CTxDestination> tx_destinations;
+
for (const CTxIn& txin : wtxIn.tx->vin) {
const COutPoint& op = txin.prevout;
- SetUsedDestinationState(op.hash, op.n, true);
+ SetUsedDestinationState(batch, op.hash, op.n, true, tx_destinations);
}
+
+ MarkDestinationsDirty(tx_destinations);
}
// Inserts only if not already there, returns tx inserted or tx found
@@ -1111,10 +802,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
wtx.m_confirm.status = wtxIn.m_confirm.status;
wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
+ wtx.m_confirm.block_height = wtxIn.m_confirm.block_height;
fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
+ assert(wtx.m_confirm.block_height == wtxIn.m_confirm.block_height);
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
@@ -1163,14 +856,24 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
void CWallet::LoadToWallet(CWalletTx& wtxIn)
{
- // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken.
+ // If wallet doesn't have a chain (e.g bitcoin-wallet), lock can't be taken.
auto locked_chain = LockChain();
- // If tx hasn't been reorged out of chain while wallet being shutdown
- // change tx status to UNCONFIRMED and reset hashBlock/nIndex.
- if (!wtxIn.m_confirm.hashBlock.IsNull()) {
- if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) {
+ if (locked_chain) {
+ Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock);
+ if (block_height) {
+ // Update cached block height variable since it not stored in the
+ // serialized transaction.
+ wtxIn.m_confirm.block_height = *block_height;
+ } else if (wtxIn.isConflicted() || wtxIn.isConfirmed()) {
+ // If tx block (or conflicting block) was reorged out of chain
+ // while the wallet was shutdown, change tx status to UNCONFIRMED
+ // and reset block height, hash, and index. ABANDONED tx don't have
+ // associated blocks and don't need to be updated. The case where a
+ // transaction was reorged out while online and then reconfirmed
+ // while offline is covered by the rescan logic.
wtxIn.setUnconfirmed();
wtxIn.m_confirm.hashBlock = uint256();
+ wtxIn.m_confirm.block_height = 0;
wtxIn.m_confirm.nIndex = 0;
}
}
@@ -1187,25 +890,25 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn)
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
if (prevtx.isConflicted()) {
- MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash());
+ MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash());
}
}
}
}
-bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate)
+bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate)
{
const CTransaction& tx = *ptx;
{
AssertLockHeld(cs_wallet);
- if (!block_hash.IsNull()) {
+ if (!confirm.hashBlock.IsNull()) {
for (const CTxIn& txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetHash()) {
- WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
- MarkConflicted(block_hash, range.first->second);
+ WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
+ MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second);
}
range.first++;
}
@@ -1224,17 +927,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St
// loop though all outputs
for (const CTxOut& txout: tx.vout) {
- // extract addresses and check if they match with an unused keypool key
- for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *this)) {
- std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
- if (mi != m_pool_key_to_index.end()) {
- WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
- MarkReserveKeysAsUsed(mi->second);
-
- if (!TopUpKeyPool()) {
- WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
- }
- }
+ for (const auto& spk_man_pair : m_spk_managers) {
+ spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey);
}
}
@@ -1242,7 +936,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
- wtx.SetConf(status, block_hash, posInBlock);
+ wtx.m_confirm = confirm;
return AddToWallet(wtx, false);
}
@@ -1255,7 +949,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
auto locked_chain = chain().lock();
LOCK(cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx);
- return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool();
+ return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
}
void CWallet::MarkInputsDirty(const CTransactionRef& tx)
@@ -1268,9 +962,9 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx)
}
}
-bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx)
+bool CWallet::AbandonTransaction(const uint256& hashTx)
{
- auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup
+ auto locked_chain = chain().lock(); // Temporary. Removed in upcoming lock cleanup
LOCK(cs_wallet);
WalletBatch batch(*database, "r+");
@@ -1282,7 +976,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
CWalletTx& origtx = it->second;
- if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) {
+ if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
@@ -1295,14 +989,13 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
auto it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx& wtx = it->second;
- int currentconfirm = wtx.GetDepthInMainChain(locked_chain);
+ int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be
assert(currentconfirm <= 0);
// if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can be in mempool
assert(!wtx.InMempool());
- wtx.m_confirm.nIndex = 0;
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
@@ -1324,12 +1017,12 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
return true;
}
-void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
+void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx)
{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- int conflictconfirms = -locked_chain->getBlockDepth(hashBlock);
+ int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1;
// If number of conflict confirms cannot be determined, this means
// that the block is still unknown or not yet part of the main chain,
// for example when loading the wallet during a reindex. Do nothing in that
@@ -1352,12 +1045,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
auto it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx& wtx = it->second;
- int currentconfirm = wtx.GetDepthInMainChain(*locked_chain);
+ int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.m_confirm.nIndex = 0;
wtx.m_confirm.hashBlock = hashBlock;
+ wtx.m_confirm.block_height = conflicting_height;
wtx.setConflicted();
wtx.MarkDirty();
batch.WriteTx(wtx);
@@ -1376,9 +1070,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
}
-void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx)
+void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx)
{
- if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx))
+ if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
@@ -1390,7 +1084,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status stat
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
+ SyncTransaction(ptx, confirm);
auto it = mapWallet.find(ptx->GetHash());
if (it != mapWallet.end()) {
@@ -1406,23 +1101,26 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
}
}
-void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) {
+void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height)
+{
const uint256& block_hash = block.GetHash();
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- for (size_t i = 0; i < block.vtx.size(); i++) {
- SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i);
- TransactionRemovedFromMempool(block.vtx[i]);
+ m_last_block_processed_height = height;
+ m_last_block_processed = block_hash;
+ for (size_t index = 0; index < block.vtx.size(); index++) {
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index);
+ SyncTransaction(block.vtx[index], confirm);
+ TransactionRemovedFromMempool(block.vtx[index]);
}
for (const CTransactionRef& ptx : vtxConflicted) {
TransactionRemovedFromMempool(ptx);
}
-
- m_last_block_processed = block_hash;
}
-void CWallet::BlockDisconnected(const CBlock& block) {
+void CWallet::BlockDisconnected(const CBlock& block, int height)
+{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
@@ -1430,8 +1128,11 @@ void CWallet::BlockDisconnected(const CBlock& block) {
// be unconfirmed, whether or not the transaction is added back to the mempool.
// User may have to call abandontransaction again. It may be addressed in the
// future with a stickier abandoned state or even removing abandontransaction call.
+ m_last_block_processed_height = height - 1;
+ m_last_block_processed = block.hashPrevBlock;
for (const CTransactionRef& ptx : block.vtx) {
- SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0);
+ SyncTransaction(ptx, confirm);
}
}
@@ -1448,7 +1149,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() {
// for the queue to drain enough to execute it (indicating we are caught up
// at least with the time we entered this function).
uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed);
- chain().waitForNotificationsIfNewBlocksConnected(last_block_hash);
+ chain().waitForNotificationsIfTipChanged(last_block_hash);
}
@@ -1487,7 +1188,21 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
isminetype CWallet::IsMine(const CTxOut& txout) const
{
- return ::IsMine(*this, txout.scriptPubKey);
+ return IsMine(txout.scriptPubKey);
+}
+
+isminetype CWallet::IsMine(const CTxDestination& dest) const
+{
+ return IsMine(GetScriptForDestination(dest));
+}
+
+isminetype CWallet::IsMine(const CScript& script) const
+{
+ isminetype result = ISMINE_NO;
+ for (const auto& spk_man_pair : m_spk_managers) {
+ result = std::max(result, spk_man_pair.second->IsMine(script));
+ }
+ return result;
}
CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const
@@ -1511,7 +1226,7 @@ bool CWallet::IsChange(const CScript& script) const
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
- if (::IsMine(*this, script))
+ if (IsMine(script))
{
CTxDestination address;
if (!ExtractDestination(script, address))
@@ -1601,92 +1316,26 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
return nChange;
}
-CPubKey CWallet::GenerateNewSeed()
-{
- assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
- CKey key;
- key.MakeNewKey(true);
- return DeriveNewSeed(key);
-}
-
-CPubKey CWallet::DeriveNewSeed(const CKey& key)
-{
- int64_t nCreationTime = GetTime();
- CKeyMetadata metadata(nCreationTime);
-
- // calculate the seed
- CPubKey seed = key.GetPubKey();
- assert(key.VerifyPubKey(seed));
-
- // set the hd keypath to "s" -> Seed, refers the seed to itself
- metadata.hdKeypath = "s";
- metadata.has_key_origin = false;
- metadata.hd_seed_id = seed.GetID();
-
- {
- LOCK(cs_wallet);
-
- // mem store the metadata
- mapKeyMetadata[seed.GetID()] = metadata;
-
- // write the key&metadata to the database
- if (!AddKeyPubKey(key, seed))
- throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
- }
-
- return seed;
-}
-
-void CWallet::SetHDSeed(const CPubKey& seed)
-{
- LOCK(cs_wallet);
- // store the keyid (hash160) together with
- // the child index counter in the database
- // as a hdchain object
- CHDChain newHdChain;
- newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
- newHdChain.seed_id = seed.GetID();
- SetHDChain(newHdChain, false);
- NotifyCanGetAddressesChanged();
- UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
-}
-
-void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
-{
- LOCK(cs_wallet);
- if (!memonly && !WalletBatch(*database).WriteHDChain(chain))
- throw std::runtime_error(std::string(__func__) + ": writing chain failed");
-
- hdChain = chain;
-}
-
bool CWallet::IsHDEnabled() const
{
- return !hdChain.seed_id.IsNull();
-}
-
-bool CWallet::CanGenerateKeys()
-{
- // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD)
- LOCK(cs_wallet);
- return IsHDEnabled() || !CanSupportFeature(FEATURE_HD);
+ bool result = true;
+ for (const auto& spk_man_pair : m_spk_managers) {
+ result &= spk_man_pair.second->IsHDEnabled();
+ }
+ return result;
}
bool CWallet::CanGetAddresses(bool internal)
{
LOCK(cs_wallet);
- // Check if the keypool has keys
- bool keypool_has_keys;
- if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) {
- keypool_has_keys = setInternalKeyPool.size() > 0;
- } else {
- keypool_has_keys = KeypoolCountExternalKeys() > 0;
- }
- // If the keypool doesn't have keys, check if we can generate them
- if (!keypool_has_keys) {
- return CanGenerateKeys();
+ if (m_spk_managers.empty()) return false;
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_man = GetScriptPubKeyMan(t, internal);
+ if (spk_man && spk_man->CanGetAddresses(internal)) {
+ return true;
+ }
}
- return keypool_has_keys;
+ return false;
}
void CWallet::SetWalletFlag(uint64_t flags)
@@ -1711,6 +1360,11 @@ void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag)
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
+void CWallet::UnsetBlankWalletFlag(WalletBatch& batch)
+{
+ UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
+}
+
bool CWallet::IsWalletFlagSet(uint64_t flag) const
{
return (m_wallet_flags & flag);
@@ -1745,7 +1399,13 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
- if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
+ std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
+ if (!provider) {
+ // We don't know about this scriptpbuKey;
+ return false;
+ }
+
+ if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateInput(tx_in, sigdata);
@@ -1770,97 +1430,53 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
{
- WalletBatch batch(*database);
- for (const auto& entry : scripts) {
- CScriptID id(entry);
- if (HaveCScript(id)) {
- WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry));
- continue;
- }
- if (!AddCScriptWithDB(batch, entry)) {
- return false;
- }
-
- if (timestamp > 0) {
- m_script_metadata[CScriptID(entry)].nCreateTime = timestamp;
- }
- }
- if (timestamp > 0) {
- UpdateTimeFirstKey(timestamp);
+ auto spk_man = GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ return false;
}
-
- return true;
+ LOCK(spk_man->cs_KeyStore);
+ return spk_man->ImportScripts(scripts, timestamp);
}
bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp)
{
- WalletBatch batch(*database);
- for (const auto& entry : privkey_map) {
- const CKey& key = entry.second;
- CPubKey pubkey = key.GetPubKey();
- const CKeyID& id = entry.first;
- assert(key.VerifyPubKey(pubkey));
- // Skip if we already have the key
- if (HaveKey(id)) {
- WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey));
- continue;
- }
- mapKeyMetadata[id].nCreateTime = timestamp;
- // If the private key is not present in the wallet, insert it.
- if (!AddKeyPubKeyWithDB(batch, key, pubkey)) {
- return false;
- }
- UpdateTimeFirstKey(timestamp);
+ auto spk_man = GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ return false;
}
- return true;
+ LOCK(spk_man->cs_KeyStore);
+ return spk_man->ImportPrivKeys(privkey_map, timestamp);
}
bool CWallet::ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp)
{
- WalletBatch batch(*database);
- for (const auto& entry : key_origins) {
- AddKeyOriginWithDB(batch, entry.second.first, entry.second.second);
- }
- for (const CKeyID& id : ordered_pubkeys) {
- auto entry = pubkey_map.find(id);
- if (entry == pubkey_map.end()) {
- continue;
- }
- const CPubKey& pubkey = entry->second;
- CPubKey temp;
- if (GetPubKey(id, temp)) {
- // Already have pubkey, skipping
- WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp));
- continue;
- }
- if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) {
- return false;
- }
- mapKeyMetadata[id].nCreateTime = timestamp;
-
- // Add to keypool only works with pubkeys
- if (add_keypool) {
- AddKeypoolPubkeyWithDB(pubkey, internal, batch);
- NotifyCanGetAddressesChanged();
- }
+ auto spk_man = GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ return false;
}
- return true;
+ LOCK(spk_man->cs_KeyStore);
+ return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins, add_keypool, internal, timestamp);
}
bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp)
{
- WalletBatch batch(*database);
- for (const CScript& script : script_pub_keys) {
- if (!have_solving_data || !::IsMine(*this, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
- if (!AddWatchOnlyWithDB(batch, script, timestamp)) {
- return false;
+ auto spk_man = GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ return false;
+ }
+ LOCK(spk_man->cs_KeyStore);
+ if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data, timestamp)) {
+ return false;
+ }
+ if (apply_label) {
+ WalletBatch batch(*database);
+ for (const CScript& script : script_pub_keys) {
+ CTxDestination dest;
+ ExtractDestination(script, dest);
+ if (IsValidDestination(dest)) {
+ SetAddressBookWithDB(batch, dest, label, "receive");
}
}
- CTxDestination dest;
- ExtractDestination(script, dest);
- if (apply_label && IsValidDestination(dest)) {
- SetAddressBookWithDB(batch, dest, label, "receive");
- }
}
return true;
}
@@ -1868,11 +1484,9 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
{
std::vector<CTxOut> txouts;
- // Look up the inputs. We should have already checked that this transaction
- // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
- // wallet, with a valid index into the vout array, and the ability to sign.
for (const CTxIn& input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.hash);
+ // Can not estimate size without knowing the input details
if (mi == wallet->mapWallet.end()) {
return -1;
}
@@ -1887,8 +1501,6 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall
{
CMutableTransaction txNew(tx);
if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
- // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
- // implies that we can sign for every input.
return -1;
}
return GetVirtualTransactionSize(CTransaction(txNew));
@@ -2068,7 +1680,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *block_height, block_hash, posInBlock);
+ SyncTransaction(block.vtx[posInBlock], confirm, fUpdate);
}
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
@@ -2116,7 +1729,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
return result;
}
-void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
+void CWallet::ReacceptWalletTransactions()
{
// If transactions aren't being broadcasted, don't let them into local mempool either
if (!fBroadcastTransactions)
@@ -2129,7 +1742,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid);
- int nDepth = wtx.GetDepthInMainChain(locked_chain);
+ int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
@@ -2140,11 +1753,11 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second);
std::string unused_err_string;
- wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain);
+ wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
}
}
-bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain)
+bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
{
// Can't relay if wallet is not broadcasting
if (!pwallet->GetBroadcastTransactions()) return false;
@@ -2154,7 +1767,7 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, in
// cause log spam.
if (IsCoinBase()) return false;
// Don't try to submit conflicted or confirmed transactions.
- if (GetDepthInMainChain(locked_chain) != 0) return false;
+ if (GetDepthInMainChain() != 0) return false;
// Submit transaction to mempool for relay
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
@@ -2189,6 +1802,7 @@ CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter
auto& amount = m_amounts[type];
if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter));
+ m_is_cache_empty = false;
}
return amount.m_value[filter];
}
@@ -2208,10 +1822,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
return debit;
}
-CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const
+CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsImmatureCoinBase(locked_chain))
+ if (IsImmatureCoinBase())
return 0;
CAmount credit = 0;
@@ -2225,16 +1839,16 @@ CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const ismine
return credit;
}
-CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const
+CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
- if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) {
+ if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
}
return 0;
}
-CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const
+CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
{
if (pwallet == nullptr)
return 0;
@@ -2243,7 +1857,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
// Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsImmatureCoinBase(locked_chain))
+ if (IsImmatureCoinBase())
return 0;
if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
@@ -2255,7 +1869,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
uint256 hashTx = GetHash();
for (unsigned int i = 0; i < tx->vout.size(); i++)
{
- if (!pwallet->IsSpent(locked_chain, hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) {
+ if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit))
@@ -2265,14 +1879,15 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo
if (allow_cache) {
m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
+ m_is_cache_empty = false;
}
return nCredit;
}
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const
+CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
{
- if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) {
+ if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
}
@@ -2295,32 +1910,37 @@ bool CWalletTx::InMempool() const
bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const
{
+ std::set<uint256> s;
+ return IsTrusted(locked_chain, s);
+}
+
+bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const
+{
// Quick answer in most cases
- if (!locked_chain.checkFinalTx(*tx)) {
- return false;
- }
- int nDepth = GetDepthInMainChain(locked_chain);
- if (nDepth >= 1)
- return true;
- if (nDepth < 0)
- return false;
- if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
- return false;
+ if (!locked_chain.checkFinalTx(*tx)) return false;
+ int nDepth = GetDepthInMainChain();
+ if (nDepth >= 1) return true;
+ if (nDepth < 0) return false;
+ // using wtx's cached debit
+ if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
- if (!InMempool())
- return false;
+ if (!InMempool()) return false;
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn& txin : tx->vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
- if (parent == nullptr)
- return false;
+ if (parent == nullptr) return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
- if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
- return false;
+ // Check that this specific input being spent is trusted
+ if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) return false;
+ // If we've already trusted this parent, continue
+ if (trusted_parents.count(parent->GetHash())) continue;
+ // Recurse to check that the parent is also trusted
+ if (!parent->IsTrusted(locked_chain, trusted_parents)) return false;
+ trusted_parents.insert(parent->GetHash());
}
return true;
}
@@ -2374,7 +1994,7 @@ void CWallet::ResendWalletTransactions()
// any confirmed or conflicting txs.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
std::string unused_err_string;
- if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count;
+ if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count;
}
} // locked_chain and cs_wallet
@@ -2406,13 +2026,14 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
{
auto locked_chain = chain().lock();
LOCK(cs_wallet);
+ std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const CWalletTx& wtx = entry.second;
- const bool is_trusted{wtx.IsTrusted(*locked_chain)};
- const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)};
- const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
- const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
+ const bool is_trusted{wtx.IsTrusted(*locked_chain, trusted_parents)};
+ const int tx_depth{wtx.GetDepthInMainChain()};
+ const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
+ const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly;
@@ -2421,8 +2042,8 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
ret.m_mine_untrusted_pending += tx_credit_mine;
ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
}
- ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain);
- ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain);
+ ret.m_mine_immature += wtx.GetImmatureCredit();
+ ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
}
}
return ret;
@@ -2456,6 +2077,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
+ std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet)
{
const uint256& wtxid = entry.first;
@@ -2465,10 +2087,10 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue;
}
- if (wtx.IsImmatureCoinBase(locked_chain))
+ if (wtx.IsImmatureCoinBase())
continue;
- int nDepth = wtx.GetDepthInMainChain(locked_chain);
+ int nDepth = wtx.GetDepthInMainChain();
if (nDepth < 0)
continue;
@@ -2477,7 +2099,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (nDepth == 0 && !wtx.InMempool())
continue;
- bool safeTx = wtx.IsTrusted(locked_chain);
+ bool safeTx = wtx.IsTrusted(locked_chain, trusted_parents);
// We should not consider coins from transactions that are replacing
// other transactions.
@@ -2528,7 +2150,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
if (IsLockedCoin(entry.first, i))
continue;
- if (IsSpent(locked_chain, wtxid, i))
+ if (IsSpent(wtxid, i))
continue;
isminetype mine = IsMine(wtx.tx->vout[i]);
@@ -2541,7 +2163,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue;
}
- bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey);
+ std::unique_ptr<SigningProvider> provider = GetSigningProvider(wtx.tx->vout[i].scriptPubKey);
+
+ bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
@@ -2574,7 +2198,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch
for (const COutput& coin : availableCoins) {
CTxDestination address;
- if (coin.fSpendable &&
+ if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
@@ -2582,12 +2206,16 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
+ // Include watch-only for LegacyScriptPubKeyMan wallets without private keys
+ const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
for (const COutPoint& output : lockedCoins) {
auto it = mapWallet.find(output.hash);
if (it != mapWallet.end()) {
- int depth = it->second.GetDepthInMainChain(locked_chain);
+ int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.n < it->second.tx->vout.size() &&
- IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) {
+ IsMine(it->second.tx->vout[output.n]) == is_mine_filter
+ ) {
CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back(
@@ -2648,7 +2276,11 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
if (effective_value > 0) {
group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
- group.effective_value += effective_value;
+ if (coin_selection_params.m_subtract_fee_outputs) {
+ group.effective_value += coin.txout.nValue;
+ } else {
+ group.effective_value += effective_value;
+ }
++it;
} else {
it = group.Discard(coin);
@@ -2674,13 +2306,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
std::vector<COutput> vCoins(vAvailableCoins);
+ CAmount value_to_select = nTargetValue;
+
+ // Default to bnb was not used. If we use it, we set it later
+ bnb_used = false;
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs)
{
- // We didn't use BnB here, so set it to false.
- bnb_used = false;
-
for (const COutput& out : vCoins)
{
if (!out.fSpendable)
@@ -2699,22 +2332,30 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs)
{
- // For now, don't use BnB if preset inputs are selected. TODO: Enable this later
- bnb_used = false;
- coin_selection_params.use_bnb = false;
-
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
if (it != mapWallet.end())
{
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
- if (wtx.tx->vout.size() <= outpoint.n)
+ if (wtx.tx->vout.size() <= outpoint.n) {
return false;
+ }
// Just to calculate the marginal byte size
- nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue;
- setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n));
- } else
+ CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false));
+ nValueFromPresetInputs += coin.txout.nValue;
+ if (coin.m_input_bytes <= 0) {
+ return false; // Not solvable, can't estimate size for fee
+ }
+ coin.effective_value = coin.txout.nValue - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
+ if (coin_selection_params.use_bnb) {
+ value_to_select -= coin.effective_value;
+ } else {
+ value_to_select -= coin.txout.nValue;
+ }
+ setPresetCoins.insert(coin);
+ } else {
return false; // TODO: Allow non-wallet inputs
+ }
}
// remove preset inputs from vCoins
@@ -2743,14 +2384,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
- bool res = nTargetValue <= nValueFromPresetInputs ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ bool res = value_to_select <= 0 ||
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
util::insert(setCoinsRet, setPresetCoins);
@@ -2775,7 +2416,14 @@ bool CWallet::SignTransaction(CMutableTransaction& tx)
const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
SignatureData sigdata;
- if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+
+ std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
+ if (!provider) {
+ // We don't know about this scriptpbuKey;
+ return false;
+ }
+
+ if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
return false;
}
UpdateInput(input, sigdata);
@@ -2927,7 +2575,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{
CAmount nValue = 0;
- ReserveDestination reservedest(this);
+ const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
+ ReserveDestination reservedest(this, change_type);
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto& recipient : vecSend)
@@ -2986,8 +2635,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
return false;
}
CTxDestination dest;
- const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
- bool ret = reservedest.GetReservedDestination(change_type, dest, true);
+ bool ret = reservedest.GetReservedDestination(dest, true);
if (!ret)
{
strFailReason = "Keypool ran out, please call keypoolrefill first";
@@ -3010,7 +2658,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
// BnB selector is the only selector used when this is true.
// That should only happen on the first pass through the loop.
- coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB
+ coin_selection_params.use_bnb = true;
+ coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values
// Start with no fee and loop until there is enough fee
while (true)
{
@@ -3024,7 +2673,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
nValueToSelect += nFeeRet;
// vouts to the payees
- coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ }
for (const auto& recipient : vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
@@ -3041,7 +2692,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
}
}
// Include the fee cost for outputs. Note this is only used for BnB right now
- coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
+ }
if (IsDust(txout, chain().relayDustFee()))
{
@@ -3060,7 +2713,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
}
// Choose coins to use
- bool bnb_used;
+ bool bnb_used = false;
if (pick_new_inputs) {
nValueIn = 0;
setCoins.clear();
@@ -3233,7 +2886,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
const CScript& scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
- if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
+ std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
+ if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
{
strFailReason = _("Signing transaction failed").translated;
return false;
@@ -3318,7 +2972,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
std::string err_string;
- if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) {
+ if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
}
@@ -3328,7 +2982,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
// Even if we don't use this lock in this function, we want to preserve
// lock order in LoadToWallet if query of chain state is needed to know
- // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation
+ // tx status. If lock can't be taken (e.g bitcoin-wallet), tx confirmation
// status may be not reliable.
auto locked_chain = LockChain();
LOCK(cs_wallet);
@@ -3339,20 +2993,17 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (database->Rewrite("\x04pool"))
{
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ for (const auto& spk_man_pair : m_spk_managers) {
+ spk_man_pair.second->RewriteDB();
+ }
}
}
- {
- LOCK(cs_KeyStore);
- // This wallet is in its first run if all of these are empty
- fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty()
- && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
+ // This wallet is in its first run if there are no ScriptPubKeyMans and it isn't blank or no privkeys
+ fFirstRunRet = m_spk_managers.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
+ if (fFirstRunRet) {
+ assert(m_external_spk_managers.empty());
+ assert(m_internal_spk_managers.empty());
}
if (nLoadWalletRet != DBErrors::LOAD_OK)
@@ -3376,12 +3027,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
{
if (database->Rewrite("\x04pool"))
{
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ for (const auto& spk_man_pair : m_spk_managers) {
+ spk_man_pair.second->RewriteDB();
+ }
}
}
@@ -3400,13 +3048,9 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
if (database->Rewrite("\x04pool"))
{
- LOCK(cs_wallet);
- setInternalKeyPool.clear();
- setExternalKeyPool.clear();
- m_pool_key_to_index.clear();
- // Note: can't top-up keypool here, because wallet is locked.
- // User will be prompted to unlock wallet the next operation
- // that requires a new key.
+ for (const auto& spk_man_pair : m_spk_managers) {
+ spk_man_pair.second->RewriteDB();
+ }
}
}
@@ -3427,7 +3071,7 @@ bool CWallet::SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& add
if (!strPurpose.empty()) /* update purpose only if requested */
mapAddressBook[address].purpose = strPurpose;
}
- NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
+ NotifyAddressBookChanged(this, address, strName, IsMine(address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
if (!strPurpose.empty() && !batch.WritePurpose(EncodeDestination(address), strPurpose))
return false;
@@ -3454,261 +3098,69 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
mapAddressBook.erase(address);
}
- NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED);
+ NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO, "", CT_DELETED);
WalletBatch(*database).ErasePurpose(EncodeDestination(address));
return WalletBatch(*database).EraseName(EncodeDestination(address));
}
-/**
- * Mark old keypool keys as used,
- * and generate all new keys
- */
-bool CWallet::NewKeyPool()
-{
- if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
- return false;
- }
- {
- LOCK(cs_wallet);
- WalletBatch batch(*database);
-
- for (const int64_t nIndex : setInternalKeyPool) {
- batch.ErasePool(nIndex);
- }
- setInternalKeyPool.clear();
-
- for (const int64_t nIndex : setExternalKeyPool) {
- batch.ErasePool(nIndex);
- }
- setExternalKeyPool.clear();
-
- for (const int64_t nIndex : set_pre_split_keypool) {
- batch.ErasePool(nIndex);
- }
- set_pre_split_keypool.clear();
-
- m_pool_key_to_index.clear();
-
- if (!TopUpKeyPool()) {
- return false;
- }
- WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n");
- }
- return true;
-}
-
size_t CWallet::KeypoolCountExternalKeys()
{
AssertLockHeld(cs_wallet);
- return setExternalKeyPool.size() + set_pre_split_keypool.size();
-}
-void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
-{
- AssertLockHeld(cs_wallet);
- if (keypool.m_pre_split) {
- set_pre_split_keypool.insert(nIndex);
- } else if (keypool.fInternal) {
- setInternalKeyPool.insert(nIndex);
- } else {
- setExternalKeyPool.insert(nIndex);
+ unsigned int count = 0;
+ for (auto spk_man : GetActiveScriptPubKeyMans()) {
+ count += spk_man->KeypoolCountExternalKeys();
}
- m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
- m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
- // If no metadata exists yet, create a default with the pool key's
- // creation time. Note that this may be overwritten by actually
- // stored metadata for that key later, which is fine.
- CKeyID keyid = keypool.vchPubKey.GetID();
- if (mapKeyMetadata.count(keyid) == 0)
- mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
+ return count;
}
-bool CWallet::TopUpKeyPool(unsigned int kpSize)
+unsigned int CWallet::GetKeyPoolSize() const
{
- if (!CanGenerateKeys()) {
- return false;
- }
- {
- LOCK(cs_wallet);
-
- if (IsLocked()) return false;
-
- // Top up key pool
- unsigned int nTargetSize;
- if (kpSize > 0)
- nTargetSize = kpSize;
- else
- nTargetSize = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
-
- // count amount of available keys (internal, external)
- // make sure the keypool of external and internal keys fits the user selected target (-keypool)
- int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);
- int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
-
- if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
- {
- // don't create extra internal keys
- missingInternal = 0;
- }
- bool internal = false;
- WalletBatch batch(*database);
- for (int64_t i = missingInternal + missingExternal; i--;)
- {
- if (i < missingInternal) {
- internal = true;
- }
+ AssertLockHeld(cs_wallet);
- CPubKey pubkey(GenerateNewKey(batch, internal));
- AddKeypoolPubkeyWithDB(pubkey, internal, batch);
- }
- if (missingInternal + missingExternal > 0) {
- WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
- }
+ unsigned int count = 0;
+ for (auto spk_man : GetActiveScriptPubKeyMans()) {
+ count += spk_man->GetKeyPoolSize();
}
- NotifyCanGetAddressesChanged();
- return true;
+ return count;
}
-void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
+bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
LOCK(cs_wallet);
- assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
- int64_t index = ++m_max_keypool_index;
- if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
- throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed");
- }
- if (internal) {
- setInternalKeyPool.insert(index);
- } else {
- setExternalKeyPool.insert(index);
- }
- m_pool_key_to_index[pubkey.GetID()] = index;
-}
-
-bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
-{
- nIndex = -1;
- keypool.vchPubKey = CPubKey();
- {
- LOCK(cs_wallet);
-
- TopUpKeyPool();
-
- bool fReturningInternal = fRequestedInternal;
- fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
- bool use_split_keypool = set_pre_split_keypool.empty();
- std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
-
- // Get the oldest key
- if (setKeyPool.empty()) {
- return false;
- }
-
- WalletBatch batch(*database);
-
- auto it = setKeyPool.begin();
- nIndex = *it;
- setKeyPool.erase(it);
- if (!batch.ReadPool(nIndex, keypool)) {
- throw std::runtime_error(std::string(__func__) + ": read failed");
- }
- CPubKey pk;
- if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
- throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
- }
- // If the key was pre-split keypool, we don't care about what type it is
- if (use_split_keypool && keypool.fInternal != fReturningInternal) {
- throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
- }
- if (!keypool.vchPubKey.IsValid()) {
- throw std::runtime_error(std::string(__func__) + ": keypool entry invalid");
- }
-
- m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
- WalletLogPrintf("keypool reserve %d\n", nIndex);
- }
- NotifyCanGetAddressesChanged();
- return true;
-}
-
-void CWallet::KeepKey(int64_t nIndex)
-{
- // Remove from key pool
- WalletBatch batch(*database);
- batch.ErasePool(nIndex);
- WalletLogPrintf("keypool keep %d\n", nIndex);
-}
-
-void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
-{
- // Return to key pool
- {
- LOCK(cs_wallet);
- if (fInternal) {
- setInternalKeyPool.insert(nIndex);
- } else if (!set_pre_split_keypool.empty()) {
- set_pre_split_keypool.insert(nIndex);
- } else {
- setExternalKeyPool.insert(nIndex);
- }
- m_pool_key_to_index[pubkey.GetID()] = nIndex;
- NotifyCanGetAddressesChanged();
+ bool res = true;
+ for (auto spk_man : GetActiveScriptPubKeyMans()) {
+ res &= spk_man->TopUp(kpSize);
}
- WalletLogPrintf("keypool return %d\n", nIndex);
-}
-
-bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
-{
- if (!CanGetAddresses(internal)) {
- return false;
- }
-
- CKeyPool keypool;
- {
- LOCK(cs_wallet);
- int64_t nIndex;
- if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
- if (IsLocked()) return false;
- WalletBatch batch(*database);
- result = GenerateNewKey(batch, internal);
- return true;
- }
- KeepKey(nIndex);
- result = keypool.vchPubKey;
- }
- return true;
+ return res;
}
bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error)
{
LOCK(cs_wallet);
error.clear();
-
- TopUpKeyPool();
-
- // Generate a new key that is added to wallet
- CPubKey new_key;
- if (!GetKeyFromPool(new_key)) {
- error = "Error: Keypool ran out, please call keypoolrefill first";
- return false;
+ bool result = false;
+ auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
+ if (spk_man) {
+ spk_man->TopUp();
+ result = spk_man->GetNewDestination(type, dest, error);
+ }
+ if (result) {
+ SetAddressBook(dest, label, "receive");
}
- LearnRelatedScripts(new_key, type);
- dest = GetDestinationForKey(new_key, type);
- SetAddressBook(dest, label, "receive");
- return true;
+ return result;
}
bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
+ LOCK(cs_wallet);
error.clear();
- TopUpKeyPool();
-
- ReserveDestination reservedest(this);
- if (!reservedest.GetReservedDestination(type, dest, true)) {
+ ReserveDestination reservedest(this, type);
+ if (!reservedest.GetReservedDestination(dest, true)) {
error = "Error: Keypool ran out, please call keypoolrefill first";
return false;
}
@@ -3717,36 +3169,28 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
return true;
}
-static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) {
- if (setKeyPool.empty()) {
- return GetTime();
- }
-
- CKeyPool keypool;
- int64_t nIndex = *(setKeyPool.begin());
- if (!batch.ReadPool(nIndex, keypool)) {
- throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
- }
- assert(keypool.vchPubKey.IsValid());
- return keypool.nTime;
-}
-
int64_t CWallet::GetOldestKeyPoolTime()
{
LOCK(cs_wallet);
+ int64_t oldestKey = std::numeric_limits<int64_t>::max();
+ for (const auto& spk_man_pair : m_spk_managers) {
+ oldestKey = std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime());
+ }
+ return oldestKey;
+}
- WalletBatch batch(*database);
-
- // load oldest key from keypool, get time and return
- int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch);
- if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) {
- oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey);
- if (!set_pre_split_keypool.empty()) {
- oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey);
+void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations) {
+ for (auto& entry : mapWallet) {
+ CWalletTx& wtx = entry.second;
+ if (wtx.m_is_cache_empty) continue;
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
+ CTxDestination dst;
+ if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, dst) && destinations.count(dst)) {
+ wtx.MarkDirty();
+ break;
+ }
}
}
-
- return oldestKey;
}
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain::Lock& locked_chain)
@@ -3755,17 +3199,18 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
{
LOCK(cs_wallet);
+ std::set<uint256> trusted_parents;
for (const auto& walletEntry : mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
- if (!wtx.IsTrusted(locked_chain))
+ if (!wtx.IsTrusted(locked_chain, trusted_parents))
continue;
- if (wtx.IsImmatureCoinBase(locked_chain))
+ if (wtx.IsImmatureCoinBase())
continue;
- int nDepth = wtx.GetDepthInMainChain(locked_chain);
+ int nDepth = wtx.GetDepthInMainChain();
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
@@ -3777,7 +3222,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain:
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue;
- CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
+ CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
@@ -3896,71 +3341,46 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
return result;
}
-bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal)
+bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal)
{
- if (!pwallet->CanGetAddresses(internal)) {
+ m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
+ if (!m_spk_man) {
return false;
}
+
if (nIndex == -1)
{
+ m_spk_man->TopUp();
+
CKeyPool keypool;
- if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
+ if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) {
return false;
}
- vchPubKey = keypool.vchPubKey;
fInternal = keypool.fInternal;
}
- assert(vchPubKey.IsValid());
- pwallet->LearnRelatedScripts(vchPubKey, type);
- address = GetDestinationForKey(vchPubKey, type);
dest = address;
return true;
}
void ReserveDestination::KeepDestination()
{
- if (nIndex != -1)
- pwallet->KeepKey(nIndex);
+ if (nIndex != -1) {
+ m_spk_man->KeepDestination(nIndex, type);
+ }
nIndex = -1;
- vchPubKey = CPubKey();
address = CNoDestination();
}
void ReserveDestination::ReturnDestination()
{
if (nIndex != -1) {
- pwallet->ReturnKey(nIndex, fInternal, vchPubKey);
+ m_spk_man->ReturnDestination(nIndex, fInternal, address);
}
nIndex = -1;
- vchPubKey = CPubKey();
address = CNoDestination();
}
-void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
-{
- AssertLockHeld(cs_wallet);
- bool internal = setInternalKeyPool.count(keypool_id);
- if (!internal) assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id));
- std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool);
- auto it = setKeyPool->begin();
-
- WalletBatch batch(*database);
- while (it != std::end(*setKeyPool)) {
- const int64_t& index = *(it);
- if (index > keypool_id) break; // set*KeyPool is ordered
-
- CKeyPool keypool;
- if (batch.ReadPool(index, keypool)) { //TODO: This should be unnecessary
- m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
- }
- LearnAllRelatedScripts(keypool.vchPubKey);
- batch.ErasePool(index);
- WalletLogPrintf("keypool index %d removed\n", index);
- it = setKeyPool->erase(it);
- }
-}
-
void CWallet::LockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
@@ -4003,8 +3423,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
+ LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan();
+ assert(spk_man != nullptr);
+ LOCK(spk_man->cs_KeyStore);
+
// get birth times for keys with metadata
- for (const auto& entry : mapKeyMetadata) {
+ for (const auto& entry : spk_man->mapKeyMetadata) {
if (entry.second.nCreateTime) {
mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
@@ -4014,7 +3438,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
const Optional<int> tip_height = locked_chain.getHeight();
const int max_height = tip_height && *tip_height > 144 ? *tip_height - 144 : 0; // the tip can be reorganized; use a 144-block safety margin
std::map<CKeyID, int> mapKeyFirstBlock;
- for (const CKeyID &keyid : GetKeys()) {
+ for (const CKeyID &keyid : spk_man->GetKeys()) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = max_height;
}
@@ -4031,7 +3455,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
- for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) {
+ for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
// ... and all their affected keys
std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && *height < rit->second)
@@ -4106,20 +3530,20 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
return nTimeSmart;
}
-bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
+bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
- return WalletBatch(*database).WriteDestData(EncodeDestination(dest), key, value);
+ return batch.WriteDestData(EncodeDestination(dest), key, value);
}
-bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
+bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
- return WalletBatch(*database).EraseDestData(EncodeDestination(dest), key);
+ return batch.EraseDestData(EncodeDestination(dest), key);
}
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
@@ -4156,24 +3580,6 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values;
}
-void CWallet::MarkPreSplitKeys()
-{
- WalletBatch batch(*database);
- for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) {
- int64_t index = *it;
- CKeyPool keypool;
- if (!batch.ReadPool(index, keypool)) {
- throw std::runtime_error(std::string(__func__) + ": read keypool entry failed");
- }
- keypool.m_pre_split = true;
- if (!batch.WritePool(index, keypool)) {
- throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed");
- }
- set_pre_split_keypool.insert(index);
- it = setExternalKeyPool.erase(it);
- }
-}
-
bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::vector<std::string>& warnings)
{
// Do some checking on wallet path. It should be either a:
@@ -4314,31 +3720,8 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
return nullptr;
}
- bool hd_upgrade = false;
- bool split_upgrade = false;
- if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) {
- walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
- walletInstance->SetMinVersion(FEATURE_HD);
-
- // generate a new master key
- CPubKey masterPubKey = walletInstance->GenerateNewSeed();
- walletInstance->SetHDSeed(masterPubKey);
- hd_upgrade = true;
- }
- // Upgrade to HD chain split if necessary
- if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) {
- walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n");
- walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
- split_upgrade = FEATURE_HD_SPLIT > prev_version;
- }
- // Mark all keys currently in the keypool as pre-split
- if (split_upgrade) {
- walletInstance->MarkPreSplitKeys();
- }
- // Regenerate the keypool if upgraded to HD
- if (hd_upgrade) {
- if (!walletInstance->TopUpKeyPool()) {
- error = _("Unable to generate keys").translated;
+ for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
+ if (!spk_man->Upgrade(prev_version, error)) {
return nullptr;
}
}
@@ -4350,16 +3733,18 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
walletInstance->SetMinVersion(FEATURE_LATEST);
walletInstance->SetWalletFlags(wallet_creation_flags, false);
- if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
- // generate a new seed
- CPubKey seed = walletInstance->GenerateNewSeed();
- walletInstance->SetHDSeed(seed);
- }
- // Top up the keypool
- if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) {
- error = _("Unable to generate initial keys").translated;
- return nullptr;
+ // Always create LegacyScriptPubKeyMan for now
+ walletInstance->SetupLegacyScriptPubKeyMan();
+
+ if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
+ LOCK(walletInstance->cs_wallet);
+ for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
+ if (!spk_man->SetupGeneration()) {
+ error = _("Unable to generate initial keys").translated;
+ return nullptr;
+ }
+ }
}
auto locked_chain = chain.lock();
@@ -4369,9 +3754,11 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
error = strprintf(_("Error loading %s: Private keys can only be disabled during creation").translated, walletFile);
return NULL;
} else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
- LOCK(walletInstance->cs_KeyStore);
- if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) {
- warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile));
+ for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
+ if (spk_man->HavePrivateKeys()) {
+ warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile));
+ break;
+ }
}
}
@@ -4492,8 +3879,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
const Optional<int> tip_height = locked_chain->getHeight();
if (tip_height) {
walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height);
+ walletInstance->m_last_block_processed_height = *tip_height;
} else {
walletInstance->m_last_block_processed.SetNull();
+ walletInstance->m_last_block_processed_height = -1;
}
if (tip_height && *tip_height != rescan_height)
@@ -4521,8 +3910,13 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
- if (walletInstance->nTimeFirstKey) {
- if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height, nullptr)) {
+ Optional<int64_t> time_first_key;
+ for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
+ int64_t time = spk_man->GetTimeFirstKey();
+ if (!time_first_key || time < *time_first_key) time_first_key = time;
+ }
+ if (time_first_key) {
+ if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) {
rescan_height = *first_block;
}
}
@@ -4562,7 +3956,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
}
}
- chain.loadWallet(interfaces::MakeWallet(walletInstance));
+ {
+ LOCK(cs_wallets);
+ for (auto& load_wallet : g_load_wallet_fns) {
+ load_wallet(interfaces::MakeWallet(walletInstance));
+ }
+ }
// Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain.
walletInstance->handleNotifications();
@@ -4590,7 +3989,7 @@ void CWallet::postInitProcess()
// Add wallet transactions that aren't already in a block to mempool
// Do this here as mempool requires genesis block to be loaded
- ReacceptWalletTransactions(*locked_chain);
+ ReacceptWalletTransactions();
// Update wallet transactions with current mempool transactions.
chain().requestMempoolTransactions(*this);
@@ -4616,55 +4015,28 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
m_pre_split = false;
}
-void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock)
-{
- // Update tx status
- m_confirm.status = status;
-
- // Update the tx's hashBlock
- m_confirm.hashBlock = block_hash;
-
- // set the position of the transaction in the block
- m_confirm.nIndex = posInBlock;
-}
-
-int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
+int CWalletTx::GetDepthInMainChain() const
{
+ assert(pwallet != nullptr);
+ AssertLockHeld(pwallet->cs_wallet);
if (isUnconfirmed() || isAbandoned()) return 0;
- return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1);
+ return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1);
}
-int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const
+int CWalletTx::GetBlocksToMaturity() const
{
if (!IsCoinBase())
return 0;
- int chain_depth = GetDepthInMainChain(locked_chain);
+ int chain_depth = GetDepthInMainChain();
assert(chain_depth >= 0); // coinbase tx should not be conflicted
return std::max(0, (COINBASE_MATURITY+1) - chain_depth);
}
-bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const
+bool CWalletTx::IsImmatureCoinBase() const
{
// note GetBlocksToMaturity is 0 for non-coinbase tx
- return GetBlocksToMaturity(locked_chain) > 0;
-}
-
-void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
-{
- if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::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)
-{
- // OutputType::P2SH_SEGWIT always adds all necessary scripts for all types.
- LearnRelatedScripts(key, OutputType::P2SH_SEGWIT);
+ return GetBlocksToMaturity() > 0;
}
std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const {
@@ -4697,44 +4069,9 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
return groups;
}
-bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const
-{
- CKeyMetadata meta;
- {
- LOCK(cs_wallet);
- auto it = mapKeyMetadata.find(keyID);
- if (it != mapKeyMetadata.end()) {
- meta = it->second;
- }
- }
- if (meta.has_key_origin) {
- std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint);
- info.path = meta.key_origin.path;
- } else { // Single pubkeys get the master fingerprint of themselves
- std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint);
- }
- return true;
-}
-
-bool CWallet::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info)
+bool CWallet::IsCrypted() const
{
- LOCK(cs_wallet);
- std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint);
- mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path;
- mapKeyMetadata[pubkey.GetID()].has_key_origin = true;
- mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path);
- return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
-}
-
-bool CWallet::SetCrypted()
-{
- LOCK(cs_KeyStore);
- if (fUseCrypto)
- return true;
- if (!mapKeys.empty())
- return false;
- fUseCrypto = true;
- return true;
+ return HasEncryptionKeys();
}
bool CWallet::IsLocked() const
@@ -4742,17 +4079,17 @@ bool CWallet::IsLocked() const
if (!IsCrypted()) {
return false;
}
- LOCK(cs_KeyStore);
+ LOCK(cs_wallet);
return vMasterKey.empty();
}
bool CWallet::Lock()
{
- if (!SetCrypted())
+ if (!IsCrypted())
return false;
{
- LOCK(cs_KeyStore);
+ LOCK(cs_wallet);
vMasterKey.clear();
}
@@ -4763,165 +4100,130 @@ bool CWallet::Lock()
bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys)
{
{
- LOCK(cs_KeyStore);
- if (!SetCrypted())
- return false;
-
- bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
- bool keyFail = false;
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
- for (; mi != mapCryptedKeys.end(); ++mi)
- {
- const CPubKey &vchPubKey = (*mi).second.first;
- const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
- CKey key;
- if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key))
- {
- keyFail = true;
- break;
+ LOCK(cs_wallet);
+ for (const auto& spk_man_pair : m_spk_managers) {
+ if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) {
+ return false;
}
- keyPass = true;
- if (fDecryptionThoroughlyChecked)
- break;
}
- if (keyPass && keyFail)
- {
- LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
- throw std::runtime_error("Error unlocking wallet: some keys decrypt but not all. Your wallet file may be corrupt.");
- }
- if (keyFail || (!keyPass && !accept_no_keys))
- return false;
vMasterKey = vMasterKeyIn;
- fDecryptionThoroughlyChecked = true;
}
NotifyStatusChanged(this);
return true;
}
-bool CWallet::HaveKey(const CKeyID &address) const
+std::set<ScriptPubKeyMan*> CWallet::GetActiveScriptPubKeyMans() const
{
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return FillableSigningProvider::HaveKey(address);
+ std::set<ScriptPubKeyMan*> spk_mans;
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_man = GetScriptPubKeyMan(t, internal);
+ if (spk_man) {
+ spk_mans.insert(spk_man);
+ }
+ }
}
- return mapCryptedKeys.count(address) > 0;
+ return spk_mans;
}
-bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const
+std::set<ScriptPubKeyMan*> CWallet::GetAllScriptPubKeyMans() const
{
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return FillableSigningProvider::GetKey(address, keyOut);
+ std::set<ScriptPubKeyMan*> spk_mans;
+ for (const auto& spk_man_pair : m_spk_managers) {
+ spk_mans.insert(spk_man_pair.second.get());
}
-
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- const CPubKey &vchPubKey = (*mi).second.first;
- const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
- return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
- }
- return false;
+ return spk_mans;
}
-bool CWallet::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const
+ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool internal) const
{
- LOCK(cs_KeyStore);
- WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
- if (it != mapWatchKeys.end()) {
- pubkey_out = it->second;
- return true;
+ const std::map<OutputType, ScriptPubKeyMan*>& spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers;
+ std::map<OutputType, ScriptPubKeyMan*>::const_iterator it = spk_managers.find(type);
+ if (it == spk_managers.end()) {
+ WalletLogPrintf("%s scriptPubKey Manager for output type %d does not exist\n", internal ? "Internal" : "External", static_cast<int>(type));
+ return nullptr;
}
- return false;
+ return it->second;
}
-bool CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
+ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const
{
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) {
- return GetWatchPubKey(address, vchPubKeyOut);
+ SignatureData sigdata;
+ for (const auto& spk_man_pair : m_spk_managers) {
+ if (spk_man_pair.second->CanProvide(script, sigdata)) {
+ return spk_man_pair.second.get();
}
- return true;
- }
-
- CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
- if (mi != mapCryptedKeys.end())
- {
- vchPubKeyOut = (*mi).second.first;
- return true;
}
- // Check for watch-only pubkeys
- return GetWatchPubKey(address, vchPubKeyOut);
+ return nullptr;
}
-std::set<CKeyID> CWallet::GetKeys() const
+ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const uint256& id) const
{
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return FillableSigningProvider::GetKeys();
+ if (m_spk_managers.count(id) > 0) {
+ return m_spk_managers.at(id).get();
}
- std::set<CKeyID> set_address;
- for (const auto& mi : mapCryptedKeys) {
- set_address.insert(mi.first);
- }
- return set_address;
+ return nullptr;
}
-bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
+std::unique_ptr<SigningProvider> CWallet::GetSigningProvider(const CScript& script) const
{
- LOCK(cs_KeyStore);
- if (!mapCryptedKeys.empty() || IsCrypted())
- return false;
+ SignatureData sigdata;
+ return GetSigningProvider(script, sigdata);
+}
- fUseCrypto = true;
- for (const KeyMap::value_type& mKey : mapKeys)
- {
- const CKey &key = mKey.second;
- CPubKey vchPubKey = key.GetPubKey();
- CKeyingMaterial vchSecret(key.begin(), key.end());
- std::vector<unsigned char> vchCryptedSecret;
- if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret))
- return false;
- if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
- return false;
+std::unique_ptr<SigningProvider> CWallet::GetSigningProvider(const CScript& script, SignatureData& sigdata) const
+{
+ for (const auto& spk_man_pair : m_spk_managers) {
+ if (spk_man_pair.second->CanProvide(script, sigdata)) {
+ return spk_man_pair.second->GetSigningProvider(script);
+ }
}
- mapKeys.clear();
- return true;
+ return nullptr;
}
-bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey)
+LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const
{
- LOCK(cs_KeyStore);
- if (!IsCrypted()) {
- return FillableSigningProvider::AddKeyPubKey(key, pubkey);
- }
+ // Legacy wallets only have one ScriptPubKeyMan which is a LegacyScriptPubKeyMan.
+ // Everything in m_internal_spk_managers and m_external_spk_managers point to the same legacyScriptPubKeyMan.
+ auto it = m_internal_spk_managers.find(OutputType::LEGACY);
+ if (it == m_internal_spk_managers.end()) return nullptr;
+ return dynamic_cast<LegacyScriptPubKeyMan*>(it->second);
+}
- if (IsLocked()) {
- return false;
- }
+LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
+{
+ SetupLegacyScriptPubKeyMan();
+ return GetLegacyScriptPubKeyMan();
+}
- std::vector<unsigned char> vchCryptedSecret;
- CKeyingMaterial vchSecret(key.begin(), key.end());
- if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) {
- return false;
+void CWallet::SetupLegacyScriptPubKeyMan()
+{
+ if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty()) {
+ return;
}
- if (!AddCryptedKey(pubkey, vchCryptedSecret)) {
- return false;
+ auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
+ for (const auto& type : OUTPUT_TYPES) {
+ m_internal_spk_managers[type] = spk_manager.get();
+ m_external_spk_managers[type] = spk_manager.get();
}
- return true;
+ m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
}
+const CKeyingMaterial& CWallet::GetEncryptionKey() const
+{
+ return vMasterKey;
+}
-bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
+bool CWallet::HasEncryptionKeys() const
{
- LOCK(cs_KeyStore);
- if (!SetCrypted()) {
- return false;
- }
+ return !mapMasterKeys.empty();
+}
- mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
- ImplicitlyLearnRelatedKeyScripts(vchPubKey);
- return true;
+void CWallet::ConnectScriptPubKeyManNotifiers()
+{
+ for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
+ spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
+ spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
+ }
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 85c277ff50..a918bb8833 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-2020 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,7 +18,7 @@
#include <validationinterface.h>
#include <wallet/coinselection.h>
#include <wallet/crypter.h>
-#include <wallet/ismine.h>
+#include <wallet/scriptpubkeyman.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
@@ -35,6 +35,8 @@
#include <boost/signals2/signal.hpp>
+using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>;
+
//! Explicitly unload and delete the wallet.
//! Blocks the current thread after signaling the unload intent so that all
//! wallet clients release the wallet.
@@ -48,6 +50,7 @@ bool HasWallets();
std::vector<std::shared_ptr<CWallet>> GetWallets();
std::shared_ptr<CWallet> GetWallet(const std::string& name);
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings);
+std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet);
enum class WalletCreationStatus {
SUCCESS,
@@ -57,8 +60,6 @@ enum class WalletCreationStatus {
WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result);
-//! Default for -keypool
-static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000;
//! -paytxfee default
constexpr CAmount DEFAULT_PAY_TX_FEE = 0;
//! -fallbackfee default
@@ -73,8 +74,6 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000;
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
//! Default for -walletrejectlongchains
static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false;
-//! Default for -avoidpartialspends
-static const bool DEFAULT_AVOIDPARTIALSPENDS = false;
//! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6;
//! -walletrbf default
@@ -99,58 +98,12 @@ struct FeeCalculation;
enum class FeeEstimateMode;
class ReserveDestination;
-/** (client) version numbers for particular wallet features */
-enum WalletFeature
-{
- FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output)
-
- FEATURE_WALLETCRYPT = 40000, // wallet encryption
- FEATURE_COMPRPUBKEY = 60000, // compressed public keys
-
- FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
-
- FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
-
- FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written
-
- FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool
-
- FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
-};
-
//! Default for -addresstype
constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::BECH32};
//! Default for -changetype
constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO};
-enum WalletFlags : uint64_t {
- // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
- // unknown wallet flags in the lower section <= (1 << 31) will be tolerated
-
- // will categorize coins as clean (not reused) and dirty (reused), and handle
- // them with privacy considerations in mind
- WALLET_FLAG_AVOID_REUSE = (1ULL << 0),
-
- // Indicates that the metadata has already been upgraded to contain key origins
- WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
-
- // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
- WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
-
- //! Flag set when a wallet contains no HD seed and no private keys, scripts,
- //! addresses, and other watch only things, and is therefore "blank."
- //!
- //! The only function this flag serves is to distinguish a blank wallet from
- //! a newly created wallet when the wallet database is loaded, to avoid
- //! initialization that should only happen on first run.
- //!
- //! This flag is also a mandatory flag to prevent previous versions of
- //! bitcoin from opening the wallet, thinking it was newly created, and
- //! then improperly reinitializing it.
- WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
-};
-
static constexpr uint64_t KNOWN_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE
| WALLET_FLAG_BLANK_WALLET
@@ -169,99 +122,6 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS;
-/** A key from a CWallet's keypool
- *
- * The wallet holds one (for pre HD-split wallets) or several keypools. These
- * are sets of keys that have not yet been used to provide addresses or receive
- * change.
- *
- * The Bitcoin Core wallet was originally a collection of unrelated private
- * keys with their associated addresses. If a non-HD wallet generated a
- * key/address, gave that address out and then restored a backup from before
- * that key's generation, then any funds sent to that address would be
- * lost definitively.
- *
- * The keypool was implemented to avoid this scenario (commit: 10384941). The
- * wallet would generate a set of keys (100 by default). When a new public key
- * was required, either to give out as an address or to use in a change output,
- * it would be drawn from the keypool. The keypool would then be topped up to
- * maintain 100 keys. This ensured that as long as the wallet hadn't used more
- * than 100 keys since the previous backup, all funds would be safe, since a
- * restored wallet would be able to scan for all owned addresses.
- *
- * A keypool also allowed encrypted wallets to give out addresses without
- * having to be decrypted to generate a new private key.
- *
- * With the introduction of HD wallets (commit: f1902510), the keypool
- * essentially became an address look-ahead pool. Restoring old backups can no
- * longer definitively lose funds as long as the addresses used were from the
- * wallet's HD seed (since all private keys can be rederived from the seed).
- * However, if many addresses were used since the backup, then the wallet may
- * not know how far ahead in the HD chain to look for its addresses. The
- * keypool is used to implement a 'gap limit'. The keypool maintains a set of
- * keys (by default 1000) ahead of the last used key and scans for the
- * addresses of those keys. This avoids the risk of not seeing transactions
- * involving the wallet's addresses, or of re-using the same address.
- *
- * The HD-split wallet feature added a second keypool (commit: 02592f4c). There
- * is an external keypool (for addresses to hand out) and an internal keypool
- * (for change addresses).
- *
- * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is
- * stored as sets of indexes in the wallet (setInternalKeyPool,
- * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the
- * index (m_pool_key_to_index). The CKeyPool object is used to
- * serialize/deserialize the pool data to/from the database.
- */
-class CKeyPool
-{
-public:
- //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB
- int64_t nTime;
- //! The public key
- CPubKey vchPubKey;
- //! Whether this keypool entry is in the internal keypool (for change outputs)
- bool fInternal;
- //! Whether this key was generated for a keypool before the wallet was upgraded to HD-split
- bool m_pre_split;
-
- CKeyPool();
- CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
-
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action) {
- int nVersion = s.GetVersion();
- if (!(s.GetType() & SER_GETHASH))
- READWRITE(nVersion);
- READWRITE(nTime);
- READWRITE(vchPubKey);
- if (ser_action.ForRead()) {
- try {
- READWRITE(fInternal);
- }
- catch (std::ios_base::failure&) {
- /* flag as external address if we can't read the internal boolean
- (this will be the case for any wallet before the HD chain split version) */
- fInternal = false;
- }
- try {
- READWRITE(m_pre_split);
- }
- catch (std::ios_base::failure&) {
- /* flag as postsplit address if we can't read the m_pre_split boolean
- (this will be the case for any wallet that upgrades to HD chain split)*/
- m_pre_split = false;
- }
- }
- else {
- READWRITE(fInternal);
- READWRITE(m_pre_split);
- }
- }
-};
-
/** A wrapper to reserve an address from a wallet
*
* ReserveDestination is used to reserve an address.
@@ -281,11 +141,12 @@ class ReserveDestination
{
protected:
//! The wallet to reserve from
- CWallet* pwallet;
+ CWallet* const pwallet;
+ //! The ScriptPubKeyMan to reserve from. Based on type when GetReservedDestination is called
+ ScriptPubKeyMan* m_spk_man{nullptr};
+ OutputType const type;
//! The index of the address's key in the keypool
int64_t nIndex{-1};
- //! The public key for the address
- CPubKey vchPubKey;
//! The destination
CTxDestination address;
//! Whether this is from the internal (change output) keypool
@@ -293,10 +154,9 @@ protected:
public:
//! Construct a ReserveDestination object. This does NOT reserve an address yet
- explicit ReserveDestination(CWallet* pwalletIn)
- {
- pwallet = pwalletIn;
- }
+ explicit ReserveDestination(CWallet* pwallet, OutputType type)
+ : pwallet(pwallet)
+ , type(type) { }
ReserveDestination(const ReserveDestination&) = delete;
ReserveDestination& operator=(const ReserveDestination&) = delete;
@@ -308,7 +168,7 @@ public:
}
//! Reserve an address
- bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal);
+ bool GetReservedDestination(CTxDestination& pubkey, bool internal);
//! Return reserved address
void ReturnDestination();
//! Keep the address. Do not return it's key to the keypool when this object goes out of scope
@@ -453,6 +313,13 @@ public:
enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS };
CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const;
mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];
+ /**
+ * This flag is true if all m_amounts caches are empty. This is particularly
+ * useful in places where MarkDirty is conditionally called and the
+ * condition can be expensive and thus can be skipped if the flag is true.
+ * See MarkDestinationsDirty.
+ */
+ mutable bool m_is_cache_empty{true};
mutable bool fChangeCached;
mutable bool fInMempool;
mutable CAmount nChangeCached;
@@ -496,14 +363,17 @@ public:
ABANDONED
};
- /* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed.
- * This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state
- * where they instead point to block hash and index of the deepest conflicting tx.
+ /* Confirmation includes tx status and a triplet of {block height/block hash/tx index in block}
+ * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned.
+ * Meaning of these fields changes with CONFLICTED state where they instead point to block hash
+ * and block height of the deepest conflicting tx.
*/
struct Confirmation {
- Status status = UNCONFIRMED;
- uint256 hashBlock = uint256();
- int nIndex = 0;
+ Status status;
+ int block_height;
+ uint256 hashBlock;
+ int nIndex;
+ Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {}
};
Confirmation m_confirm;
@@ -546,7 +416,6 @@ public:
* compatibility (pre-commit 9ac63d6).
*/
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
- m_confirm.hashBlock = uint256();
setAbandoned();
} else if (serializedIndex == -1) {
setConflicted();
@@ -577,6 +446,7 @@ public:
m_amounts[IMMATURE_CREDIT].Reset();
m_amounts[AVAILABLE_CREDIT].Reset();
fChangeCached = false;
+ m_is_cache_empty = true;
}
void BindWallet(CWallet *pwalletIn)
@@ -587,14 +457,14 @@ public:
//! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const;
- CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const;
- CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const;
+ CAmount GetCredit(const isminefilter& filter) const;
+ CAmount GetImmatureCredit(bool fUseCache = true) const;
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The
// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid
// having to resolve the issue of member access into incomplete type CWallet.
- CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS;
- CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const;
+ CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS;
+ CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const;
CAmount GetChange() const;
// Get the marginal bytes if spending the specified output from this transaction
@@ -616,11 +486,12 @@ public:
bool InMempool() const;
bool IsTrusted(interfaces::Chain::Lock& locked_chain) const;
+ bool IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const;
int64_t GetTxTime() const;
// Pass this transaction to node for mempool insertion and relay to peers if flag set to true
- bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain);
+ bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay);
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
@@ -630,38 +501,44 @@ public:
// in place.
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
- void SetConf(Status status, const uint256& block_hash, int posInBlock);
-
/**
* Return depth of transaction in blockchain:
* <0 : conflicts with a transaction this deep in the blockchain
* 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain
*/
- int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const;
- bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; }
+ // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
+ // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
+ // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
+ // resolve the issue of member access into incomplete type CWallet. Note
+ // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
+ // in place.
+ int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS;
+ bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
/**
* @return number of blocks to maturity for this transaction:
* 0 : is not a coinbase transaction, or is a mature coinbase transaction
* >0 : is a coinbase transaction which matures in this many blocks
*/
- int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const;
+ int GetBlocksToMaturity() const;
bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
void setAbandoned()
{
m_confirm.status = CWalletTx::ABANDONED;
m_confirm.hashBlock = uint256();
+ m_confirm.block_height = 0;
m_confirm.nIndex = 0;
}
bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
+ bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; }
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); }
- bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const;
+ bool IsImmatureCoinBase() const;
};
class COutput
@@ -715,6 +592,8 @@ struct CoinSelectionParams
size_t change_spend_size = 0;
CFeeRate effective_fee = CFeeRate(0);
size_t tx_noinputs_size = 0;
+ //! Indicate that we are subtracting the fee from outputs
+ bool m_subtract_fee_outputs = false;
CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {}
CoinSelectionParams() {}
@@ -722,37 +601,15 @@ struct CoinSelectionParams
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.
+ * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions.
*/
-class CWallet final : public FillableSigningProvider, private interfaces::Chain::Notifications
+class CWallet final : public WalletStorage, private interfaces::Chain::Notifications
{
private:
- CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore);
-
- //! if fUseCrypto is true, mapKeys must be empty
- //! if fUseCrypto is false, vMasterKey must be empty
- std::atomic<bool> fUseCrypto;
-
- //! keeps track of whether Unlock has run a thorough check before
- bool fDecryptionThoroughlyChecked;
-
- using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
- using WatchOnlySet = std::set<CScript>;
- using WatchKeyMap = std::map<CKeyID, CPubKey>;
+ CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet);
- bool SetCrypted();
-
- //! will encrypt previously unencrypted keys
- bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);
bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false);
- CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
- WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
- WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
-
- bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey);
std::atomic<bool> fAbortRescan{false};
std::atomic<bool> fScanningWallet{false}; // controlled by WalletRescanReserver
@@ -761,8 +618,6 @@ private:
std::mutex mutexScanning;
friend class WalletRescanReserver;
- WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr;
-
//! the current wallet version: clients below this version are not able to load the wallet
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
@@ -798,10 +653,10 @@ private:
* Abandoned state should probably be more carefully tracked via different
* posInBlock signals or by checking mempool presence when necessary.
*/
- bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
- void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
+ void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx);
/* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */
void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -810,55 +665,18 @@ private:
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
- void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- /* the HD chain data model (external chain counters) */
- CHDChain hdChain;
-
- /* HD derive new child key (on internal or external chain) */
- void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet);
- std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet);
- std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet);
- int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0;
- std::map<CKeyID, int64_t> m_pool_key_to_index;
std::atomic<uint64_t> m_wallet_flags{0};
- int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0;
-
- /**
- * Private version of AddWatchOnly method which does not accept a
- * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
- * the watch key did not previously have a timestamp associated with it.
- * Because this is an inherited virtual method, it is accessible despite
- * being marked private, but it is marked private anyway to encourage use
- * of the other AddWatchOnly which accepts a timestamp and sets
- * nTimeFirstKey more intelligently for more efficient rescans.
- */
- bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool AddWatchOnlyInMem(const CScript &dest);
-
- /** Add a KeyOriginInfo to the wallet */
- bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
-
- //! Adds a key to the store, and saves it to disk.
- bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- //! Adds a watch-only address to the store, and saves it to disk.
- bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch);
-
bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose);
- //! Adds a script to the store and saves it to disk
- bool AddCScriptWithDB(WalletBatch& batch, const CScript& script);
-
//! Unsets a wallet flag and saves it to disk
void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag);
+ //! Unset the blank wallet flag and saves it to disk
+ void UnsetBlankWalletFlag(WalletBatch& batch) override;
+
/** Interface for accessing chain state. */
interfaces::Chain* m_chain;
@@ -872,21 +690,31 @@ private:
* The following is used to keep track of how far behind the wallet is
* from the chain sync, and to allow clients to block on us being caught up.
*
- * Note that this is *not* how far we've processed, we may need some rescan
- * to have seen all transactions in the chain, but is only used to track
- * live BlockConnected callbacks.
+ * Processed hash is a pointer on node's tip and doesn't imply that the wallet
+ * has scanned sequentially all blocks up to this one.
*/
uint256 m_last_block_processed GUARDED_BY(cs_wallet);
- //! Fetches a key from the keypool
- bool GetKeyFromPool(CPubKey &key, bool internal = false);
+ /* Height of last block processed is used by wallet to know depth of transactions
+ * without relying on Chain interface beyond asynchronous updates. For safety, we
+ * initialize it to -1. Height is a pointer on node's tip and doesn't imply
+ * that the wallet has scanned sequentially all blocks up to this one.
+ */
+ int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1;
+
+ std::map<OutputType, ScriptPubKeyMan*> m_external_spk_managers;
+ std::map<OutputType, ScriptPubKeyMan*> m_internal_spk_managers;
+
+ // Indexed by a unique identifier produced by each ScriptPubKeyMan using
+ // ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
+ std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
public:
/*
* Main wallet lock.
* This lock protects all the fields added by CWallet.
*/
- mutable CCriticalSection cs_wallet;
+ mutable RecursiveMutex cs_wallet;
/** Get database handle used by this wallet. Ideally this function would
* not be necessary.
@@ -895,6 +723,7 @@ public:
{
return *database;
}
+ WalletDatabase& GetDatabase() override { return *database; }
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
@@ -910,24 +739,13 @@ public:
*/
const std::string& GetName() const { return m_location.GetName(); }
- void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- // Map from Key ID to key metadata.
- std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet);
-
- // Map from Script ID to key metadata (for watch-only keys).
- std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet);
-
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID = 0;
/** Construct wallet with specified name and database implementation. */
CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database)
- : fUseCrypto(false),
- fDecryptionThoroughlyChecked(false),
- m_chain(chain),
+ : m_chain(chain),
m_location(location),
database(std::move(database))
{
@@ -937,12 +755,10 @@ public:
{
// Should not have slots connected at this point.
assert(NotifyUnload.empty());
- delete encrypted_batch;
- encrypted_batch = nullptr;
}
- bool IsCrypted() const { return fUseCrypto; }
- bool IsLocked() const;
+ bool IsCrypted() const;
+ bool IsLocked() const override;
bool Lock();
/** Interface to assert chain access and if successful lock it */
@@ -972,7 +788,7 @@ public:
const CWalletTx* GetWalletTx(const uint256& hash) const;
//! check whether we are allowed to upgrade (or already support) to the named feature
- bool CanSupportFeature(enum WalletFeature wf) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
+ bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
/**
* populate vCoins with vector of available COutputs.
@@ -998,12 +814,11 @@ public:
bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
- bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- // Whether this or any UTXO with the same CTxDestination has been spent.
- bool IsUsedDestination(const CTxDestination& dst) const;
- bool IsUsedDestination(const uint256& hash, unsigned int n) const;
- void SetUsedDestinationState(const uint256& hash, unsigned int n, bool used);
+ // Whether this or any known UTXO with the same single key has been spent.
+ bool IsUsedDestination(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const;
@@ -1022,39 +837,15 @@ public:
int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; }
double ScanningProgress() const { return fScanningWallet ? (double) m_scanning_progress : 0; }
- /**
- * keystore implementation
- * Generate a new key
- */
- CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Adds a key to the store, and saves it to disk.
- bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Adds a key to the store, without saving it to disk (used by LoadWallet)
- bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); }
- //! Load metadata (used by LoadWallet)
- void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
- void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
-
- //! Adds an encrypted key to the store, and saves it to disk.
- bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
- bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
- bool GetKey(const CKeyID &address, CKey& keyOut) const override;
- bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override;
- bool HaveKey(const CKeyID &address) const override;
- std::set<CKeyID> GetKeys() const override;
- bool AddCScript(const CScript& redeemScript) override;
- bool LoadCScript(const CScript& redeemScript);
//! Adds a destination data tuple to the store, and saves it to disk
- bool AddDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool AddDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Erases a destination data tuple in the store and on disk
- bool EraseDestData(const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool EraseDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a destination data tuple to the store, without saving it to disk
void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Look up a destination data tuple in the store, return true if found false otherwise
@@ -1062,18 +853,6 @@ public:
//! Get all destination values matching a prefix.
std::vector<std::string> GetDestValues(const std::string& prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Adds a watch-only address to the store, and saves it to disk.
- bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
- bool LoadWatchOnly(const CScript &dest);
- //! Returns whether the watch-only script is in the wallet
- bool HaveWatchOnly(const CScript &dest) const;
- //! Returns whether there are any watch-only things in the wallet
- bool HaveWatchOnly() const;
- //! Fetches a pubkey from mapWatchKeys if it exists there
- bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const;
-
//! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock().
int64_t nRelockTime = 0;
@@ -1095,8 +874,8 @@ public:
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void TransactionAddedToMempool(const CTransactionRef& tx) override;
- void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
- void BlockDisconnected(const CBlock& block) override;
+ void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height) override;
+ void BlockDisconnected(const CBlock& block, int height) override;
void UpdatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
@@ -1117,7 +896,7 @@ public:
};
ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate);
void TransactionRemovedFromMempool(const CTransactionRef &ptx) override;
- void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
@@ -1151,9 +930,9 @@ public:
* Should be called after CreateTransaction unless you want to abort
* broadcasting the transaction.
*
- * @param tx[in] The transaction to be broadcast.
- * @param mapValue[in] key-values to be set on the transaction.
- * @param orderForm[in] BIP 70 / BIP 21 order form details to be set on the transaction.
+ * @param[in] tx The transaction to be broadcast.
+ * @param[in] mapValue key-values to be set on the transaction.
+ * @param[in] orderForm BIP 70 / BIP 21 order form details to be set on the transaction.
*/
void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm);
@@ -1189,42 +968,27 @@ public:
/** Absolute maximum transaction fee (in satoshis) used by default for the wallet */
CAmount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE};
- bool NewKeyPool();
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool TopUpKeyPool(unsigned int kpSize = 0);
- /**
- * Reserves a key from the keypool and sets nIndex to its index
- *
- * @param[out] nIndex the index of the key in keypool
- * @param[out] keypool the keypool the key was drawn from, which could be the
- * the pre-split pool if present, or the internal or external pool
- * @param fRequestedInternal true if the caller would like the key drawn
- * from the internal keypool, false if external is preferred
- *
- * @return true if succeeded, false if failed due to empty keypool
- * @throws std::runtime_error if keypool read failed, key was invalid,
- * was not found in the wallet, or was misclassified in the internal
- * or external keypool
- */
- bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
- void KeepKey(int64_t nIndex);
- void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey);
int64_t GetOldestKeyPoolTime();
- /**
- * Marks all keys in the keypool up to and including reserve_key as used.
- */
- void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::map<CTxDestination, CAmount> GetAddressBalances(interfaces::Chain::Lock& locked_chain);
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
+ /**
+ * Marks all outputs in each one of the destinations dirty, so their cache is
+ * reset and does not return outdated information.
+ */
+ void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error);
bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, std::string& error);
+ isminetype IsMine(const CTxDestination& dest) const;
+ isminetype IsMine(const CScript& script) const;
isminetype IsMine(const CTxIn& txin) const;
/**
* Returns amount of debit if the input matches the
@@ -1254,14 +1018,10 @@ public:
bool DelAddressBook(const CTxDestination& address);
- unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
- {
- AssertLockHeld(cs_wallet);
- return setInternalKeyPool.size() + setExternalKeyPool.size();
- }
+ unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
- void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false);
+ void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override;
//! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
bool SetMaxVersion(int nVersion);
@@ -1321,7 +1081,7 @@ public:
bool TransactionCanBeAbandoned(const uint256& hashTx) const;
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
- bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx);
+ bool AbandonTransaction(const uint256& hashTx);
/** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
@@ -1340,31 +1100,12 @@ public:
bool BackupWallet(const std::string& strDest);
- /* Set the HD chain model (chain child index counters) */
- void SetHDChain(const CHDChain& chain, bool memonly);
- const CHDChain& GetHDChain() const { return hdChain; }
-
/* Returns true if HD is enabled */
bool IsHDEnabled() const;
- /* Returns true if the wallet can generate new keys */
- bool CanGenerateKeys();
-
/* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
bool CanGetAddresses(bool internal = false);
- /* Generates a new HD seed (will not be activated) */
- CPubKey GenerateNewSeed();
-
- /* Derives a new HD seed (will not be activated) */
- CPubKey DeriveNewSeed(const CKey& key);
-
- /* Set the current HD seed (will reset the chain child index counters)
- Sets the seed's version based on the current wallet version (so the
- caller must ensure the current wallet version is correct before calling
- this function). */
- void SetHDSeed(const CPubKey& key);
-
/**
* Blocks until the wallet state is up-to-date to /at least/ the current
* chain at the time this function is entered
@@ -1373,20 +1114,6 @@ public:
*/
void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_main, cs_wallet);
- /**
- * 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 FillableSigningProvider 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);
-
/** set a single wallet flag */
void SetWalletFlag(uint64_t flags);
@@ -1394,14 +1121,14 @@ public:
void UnsetWalletFlag(uint64_t flag);
/** check if a certain wallet flag is set */
- bool IsWalletFlagSet(uint64_t flag) const;
+ bool IsWalletFlagSet(uint64_t flag) const override;
/** overwrite all flags by the given uint64_t
returns false if unknown, non-tolerable flags are present */
bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
/** Returns a bracketed wallet name for displaying in logs, will return [default wallet] if the wallet has no name */
- const std::string GetDisplayName() const {
+ const std::string GetDisplayName() const override {
std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName();
return strprintf("[%s]", wallet_name);
};
@@ -1412,8 +1139,51 @@ public:
LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...);
};
- /** Implement lookup of key origin information through wallet key metadata. */
- bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
+ //! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers
+ std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const;
+
+ //! Returns all unique ScriptPubKeyMans
+ std::set<ScriptPubKeyMan*> GetAllScriptPubKeyMans() const;
+
+ //! Get the ScriptPubKeyMan for the given OutputType and internal/external chain.
+ ScriptPubKeyMan* GetScriptPubKeyMan(const OutputType& type, bool internal) const;
+
+ //! Get the ScriptPubKeyMan for a script
+ ScriptPubKeyMan* GetScriptPubKeyMan(const CScript& script) const;
+ //! Get the ScriptPubKeyMan by id
+ ScriptPubKeyMan* GetScriptPubKeyMan(const uint256& id) const;
+
+ //! Get the SigningProvider for a script
+ std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const;
+ std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script, SignatureData& sigdata) const;
+
+ //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
+ LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;
+ LegacyScriptPubKeyMan* GetOrCreateLegacyScriptPubKeyMan();
+
+ //! Make a LegacyScriptPubKeyMan and set it for all types, internal, and external.
+ void SetupLegacyScriptPubKeyMan();
+
+ const CKeyingMaterial& GetEncryptionKey() const override;
+ bool HasEncryptionKeys() const override;
+
+ /** Get last block processed height */
+ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
+ {
+ AssertLockHeld(cs_wallet);
+ assert(m_last_block_processed_height >= 0);
+ return m_last_block_processed_height;
+ };
+ /** Set last block processed height, currently only use in unit test */
+ void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet)
+ {
+ AssertLockHeld(cs_wallet);
+ m_last_block_processed_height = block_height;
+ m_last_block_processed = block_hash;
+ };
+
+ //! Connect the signals from ScriptPubKeyMans to the signals in CWallet
+ void ConnectScriptPubKeyManNotifiers();
};
/**
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index a9e6763c6d..a1928f45c4 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -250,8 +250,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssKey >> script;
char fYes;
ssValue >> fYes;
- if (fYes == '1')
- pwallet->LoadWatchOnly(script);
+ if (fYes == '1') {
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script);
+ }
} else if (strType == DBKeys::KEY) {
CPubKey vchPubKey;
ssKey >> vchPubKey;
@@ -302,12 +303,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
- if (!pwallet->LoadKey(key, vchPubKey))
+ if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKey(key, vchPubKey))
{
- strErr = "Error reading wallet database: LoadKey failed";
+ strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed";
return false;
}
} else if (strType == DBKeys::MASTER_KEY) {
+ // Master encryption key is loaded into only the wallet and not any of the ScriptPubKeyMans.
unsigned int nID;
ssKey >> nID;
CMasterKey kMasterKey;
@@ -332,9 +334,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssValue >> vchPrivKey;
wss.nCKeys++;
- if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
+ if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey))
{
- strErr = "Error reading wallet database: LoadCryptedKey failed";
+ strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";
return false;
}
wss.fIsEncrypted = true;
@@ -344,14 +346,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
- pwallet->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
} else if (strType == DBKeys::WATCHMETA) {
CScript script;
ssKey >> script;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
- pwallet->LoadScriptMetadata(CScriptID(script), keyMeta);
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta);
} else if (strType == DBKeys::DEFAULTKEY) {
// We don't want or need the default key, but if there is one set,
// we want to make sure that it is valid so that we can detect corruption
@@ -367,15 +369,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
CKeyPool keypool;
ssValue >> keypool;
- pwallet->LoadKeyPool(nIndex, keypool);
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool);
} else if (strType == DBKeys::CSCRIPT) {
uint160 hash;
ssKey >> hash;
CScript script;
ssValue >> script;
- if (!pwallet->LoadCScript(script))
+ if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCScript(script))
{
- strErr = "Error reading wallet database: LoadCScript failed";
+ strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCScript failed";
return false;
}
} else if (strType == DBKeys::ORDERPOSNEXT) {
@@ -389,7 +391,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
} else if (strType == DBKeys::HDCHAIN) {
CHDChain chain;
ssValue >> chain;
- pwallet->SetHDChain(chain, true);
+ pwallet->GetOrCreateLegacyScriptPubKeyMan()->SetHDChain(chain, true);
} else if (strType == DBKeys::FLAGS) {
uint64_t flags;
ssValue >> flags;
@@ -512,8 +514,13 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records);
// nTimeFirstKey is only reliable if all keys have metadata
- if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta)
- pwallet->UpdateTimeFirstKey(1);
+ if ((wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) {
+ auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan();
+ if (spk_man) {
+ LOCK(spk_man->cs_KeyStore);
+ spk_man->UpdateTimeFirstKey(1);
+ }
+ }
for (const uint256& hash : wss.vWalletUpgrade)
WriteTx(pwallet->mapWallet.at(hash));
@@ -622,7 +629,7 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u
}
else if ((*it) == hash) {
if(!EraseTx(hash)) {
- LogPrint(BCLog::DB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
+ LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
delerror = true;
}
vTxHashOut.push_back(hash);
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index b1781d5ccf..1a65125480 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -124,7 +124,7 @@ public:
std::string hdKeypath; //optional HD/bip32 keypath. Still used to determine whether a key is a seed. Also kept for backwards compatibility
CKeyID hd_seed_id; //id of the HD seed used to derive this key
KeyOriginInfo key_origin; // Key origin info with path and fingerprint
- bool has_key_origin = false; //< Whether the key_origin is useful
+ bool has_key_origin = false; //!< Whether the key_origin is useful
CKeyMetadata()
{
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 0843194511..fbfdf9dd6b 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -27,18 +27,20 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::
}
// dummy chain interface
std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet);
+ LOCK(wallet_instance->cs_wallet);
bool first_run = true;
DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run);
if (load_wallet_ret != DBErrors::LOAD_OK) {
- tfm::format(std::cerr, "Error creating %s", name.c_str());
+ tfm::format(std::cerr, "Error creating %s", name);
return nullptr;
}
wallet_instance->SetMinVersion(FEATURE_HD_SPLIT);
// generate a new HD seed
- CPubKey seed = wallet_instance->GenerateNewSeed();
- wallet_instance->SetHDSeed(seed);
+ auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
+ CPubKey seed = spk_man->GenerateNewSeed();
+ spk_man->SetHDSeed(seed);
tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
@@ -59,28 +61,28 @@ static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::pa
bool first_run;
load_wallet_ret = wallet_instance->LoadWallet(first_run);
} catch (const std::runtime_error&) {
- tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name.c_str());
+ tfm::format(std::cerr, "Error loading %s. Is wallet being used by another process?\n", name);
return nullptr;
}
if (load_wallet_ret != DBErrors::LOAD_OK) {
wallet_instance = nullptr;
if (load_wallet_ret == DBErrors::CORRUPT) {
- tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name.c_str());
+ tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name);
return nullptr;
} else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) {
tfm::format(std::cerr, "Error reading %s! All keys read correctly, but transaction data"
" or address book entries might be missing or incorrect.",
- name.c_str());
+ name);
} else if (load_wallet_ret == DBErrors::TOO_NEW) {
tfm::format(std::cerr, "Error loading %s: Wallet requires newer version of %s",
- name.c_str(), PACKAGE_NAME);
+ name, PACKAGE_NAME);
return nullptr;
} else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
tfm::format(std::cerr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME);
return nullptr;
} else {
- tfm::format(std::cerr, "Error loading %s", name.c_str());
+ tfm::format(std::cerr, "Error loading %s", name);
return nullptr;
}
}
@@ -94,7 +96,7 @@ static void WalletShowInfo(CWallet* wallet_instance)
tfm::format(std::cout, "Wallet info\n===========\n");
tfm::format(std::cout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no");
- tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain().seed_id.IsNull() ? "no" : "yes");
+ tfm::format(std::cout, "HD (hd seed available): %s\n", wallet_instance->IsHDEnabled() ? "yes" : "no");
tfm::format(std::cout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize());
tfm::format(std::cout, "Transactions: %zu\n", wallet_instance->mapWallet.size());
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size());
@@ -112,12 +114,12 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
}
} else if (command == "info") {
if (!fs::exists(path)) {
- tfm::format(std::cerr, "Error: no wallet file at %s\n", name.c_str());
+ tfm::format(std::cerr, "Error: no wallet file at %s\n", name);
return false;
}
std::string error;
if (!WalletBatch::VerifyEnvironment(path, error)) {
- tfm::format(std::cerr, "Error loading %s. Is wallet being used by other process?\n", name.c_str());
+ tfm::format(std::cerr, "Error loading %s. Is wallet being used by other process?\n", name);
return false;
}
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
@@ -125,7 +127,7 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
WalletShowInfo(wallet_instance.get());
wallet_instance->Flush(true);
} else {
- tfm::format(std::cerr, "Invalid command: %s\n", command.c_str());
+ tfm::format(std::cerr, "Invalid command: %s\n", command);
return false;
}
diff --git a/src/wallet/wallettool.h b/src/wallet/wallettool.h
index bd08da42d6..8ee3355f02 100644
--- a/src/wallet/wallettool.h
+++ b/src/wallet/wallettool.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 The Bitcoin Core developers
+// Copyright (c) 2016-2019 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.h b/src/wallet/walletutil.h
index ba2f913841..c91c9aca96 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2017-2018 The Bitcoin Core developers
+// Copyright (c) 2017-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -9,6 +9,54 @@
#include <vector>
+/** (client) version numbers for particular wallet features */
+enum WalletFeature
+{
+ FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getwalletinfo's clientversion output)
+
+ FEATURE_WALLETCRYPT = 40000, // wallet encryption
+ FEATURE_COMPRPUBKEY = 60000, // compressed public keys
+
+ FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
+
+ FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
+
+ FEATURE_NO_DEFAULT_KEY = 159900, // Wallet without a default key written
+
+ FEATURE_PRE_SPLIT_KEYPOOL = 169900, // Upgraded to HD SPLIT and can have a pre-split keypool
+
+ FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
+};
+
+
+
+enum WalletFlags : uint64_t {
+ // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
+ // unknown wallet flags in the lower section <= (1 << 31) will be tolerated
+
+ // will categorize coins as clean (not reused) and dirty (reused), and handle
+ // them with privacy considerations in mind
+ WALLET_FLAG_AVOID_REUSE = (1ULL << 0),
+
+ // Indicates that the metadata has already been upgraded to contain key origins
+ WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1),
+
+ // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
+ WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
+
+ //! Flag set when a wallet contains no HD seed and no private keys, scripts,
+ //! addresses, and other watch only things, and is therefore "blank."
+ //!
+ //! The only function this flag serves is to distinguish a blank wallet from
+ //! a newly created wallet when the wallet database is loaded, to avoid
+ //! initialization that should only happen on first run.
+ //!
+ //! This flag is also a mandatory flag to prevent previous versions of
+ //! bitcoin from opening the wallet, thinking it was newly created, and
+ //! then improperly reinitializing it.
+ WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
+};
+
//! Get the path of the wallet directory.
fs::path GetWalletDir();