diff options
Diffstat (limited to 'src')
77 files changed, 2558 insertions, 2030 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 82cc19d57c..619f968bc9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -157,6 +157,7 @@ BITCOIN_CORE_H = \ netmessagemaker.h \ node/coin.h \ node/coinstats.h \ + node/context.h \ node/psbt.h \ node/transaction.h \ noui.h \ @@ -236,6 +237,7 @@ BITCOIN_CORE_H = \ wallet/load.h \ wallet/psbtwallet.h \ wallet/rpcwallet.h \ + wallet/scriptpubkeyman.h \ wallet/wallet.h \ wallet/walletdb.h \ wallet/wallettool.h \ @@ -284,6 +286,7 @@ libbitcoin_server_a_SOURCES = \ net_processing.cpp \ node/coin.cpp \ node/coinstats.cpp \ + node/context.cpp \ node/psbt.cpp \ node/transaction.cpp \ noui.cpp \ @@ -339,11 +342,11 @@ libbitcoin_wallet_a_SOURCES = \ wallet/db.cpp \ wallet/feebumper.cpp \ wallet/fees.cpp \ - wallet/ismine.cpp \ wallet/load.cpp \ wallet/psbtwallet.cpp \ wallet/rpcdump.cpp \ wallet/rpcwallet.cpp \ + wallet/scriptpubkeyman.cpp \ wallet/wallet.cpp \ wallet/walletdb.cpp \ wallet/walletutil.cpp \ diff --git a/src/banman.h b/src/banman.h index a1a00309dd..9d45bf0559 100644 --- a/src/banman.h +++ b/src/banman.h @@ -66,5 +66,4 @@ private: const int64_t m_default_ban_time; }; -extern std::unique_ptr<BanMan> g_banman; #endif diff --git a/src/bench/bench_bitcoin.cpp b/src/bench/bench_bitcoin.cpp index 655a5a3459..9235d5fe6a 100644 --- a/src/bench/bench_bitcoin.cpp +++ b/src/bench/bench_bitcoin.cpp @@ -36,7 +36,7 @@ int main(int argc, char** argv) SetupBenchArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); return EXIT_FAILURE; } @@ -60,7 +60,7 @@ int main(int argc, char** argv) double scaling_factor; if (!ParseDouble(scaling_str, &scaling_factor)) { - tfm::format(std::cerr, "Error parsing scaling factor as double: %s\n", scaling_str.c_str()); + tfm::format(std::cerr, "Error parsing scaling factor as double: %s\n", scaling_str); return EXIT_FAILURE; } diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index f2ab03e20e..29a145bfe6 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -4,6 +4,7 @@ #include <bench/bench.h> #include <interfaces/chain.h> +#include <node/context.h> #include <wallet/coinselection.h> #include <wallet/wallet.h> @@ -28,7 +29,8 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st // (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484) static void CoinSelection(benchmark::State& state) { - auto chain = interfaces::MakeChain(); + NodeContext node; + auto chain = interfaces::MakeChain(node); const CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxs; LOCK(wallet.cs_wallet); @@ -60,7 +62,8 @@ static void CoinSelection(benchmark::State& state) } typedef std::set<CInputCoin> CoinSet; -static auto testChain = interfaces::MakeChain(); +static NodeContext testNode; +static auto testChain = interfaces::MakeChain(testNode); static const CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); std::vector<std::unique_ptr<CWalletTx>> wtxn; diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index 313b5a3ba0..0e660d6bcd 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -4,6 +4,7 @@ #include <bench/bench.h> #include <interfaces/chain.h> +#include <node/context.h> #include <optional.h> #include <test/util.h> #include <validationinterface.h> @@ -13,7 +14,8 @@ static void WalletBalance(benchmark::State& state, const bool set_dirty, const b { const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE; - std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(); + NodeContext node; + std::unique_ptr<interfaces::Chain> chain = interfaces::MakeChain(node); CWallet wallet{chain.get(), WalletLocation(), WalletDatabase::CreateMock()}; { bool first_run; diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 93b7a7152c..d7b6891503 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -105,7 +105,7 @@ static int AppInitRPC(int argc, char* argv[]) SetupCliArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); return EXIT_FAILURE; } if (argc < 2 || HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { @@ -119,7 +119,7 @@ static int AppInitRPC(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); } - tfm::format(std::cout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage); if (argc < 2) { tfm::format(std::cerr, "Error: too few parameters\n"); return EXIT_FAILURE; @@ -127,11 +127,11 @@ static int AppInitRPC(int argc, char* argv[]) return EXIT_SUCCESS; } if (!CheckDataDirOption()) { - tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); + tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")); return EXIT_FAILURE; } if (!gArgs.ReadConfigFiles(error, true)) { - tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str()); + tfm::format(std::cerr, "Error reading configuration file: %s\n", error); return EXIT_FAILURE; } // Check for -chain, -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) @@ -258,6 +258,8 @@ public: result.pushKV("version", batch[ID_NETWORKINFO]["result"]["version"]); result.pushKV("protocolversion", batch[ID_NETWORKINFO]["result"]["protocolversion"]); result.pushKV("blocks", batch[ID_BLOCKCHAININFO]["result"]["blocks"]); + result.pushKV("headers", batch[ID_BLOCKCHAININFO]["result"]["headers"]); + result.pushKV("verificationprogress", batch[ID_BLOCKCHAININFO]["result"]["verificationprogress"]); result.pushKV("timeoffset", batch[ID_NETWORKINFO]["result"]["timeoffset"]); result.pushKV("connections", batch[ID_NETWORKINFO]["result"]["connections"]); result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]); @@ -366,7 +368,7 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co std::string endpoint = "/"; if (!gArgs.GetArgs("-rpcwallet").empty()) { std::string walletName = gArgs.GetArg("-rpcwallet", ""); - char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false); + char *encodedURI = evhttp_uriencode(walletName.data(), walletName.size(), false); if (encodedURI) { endpoint = "/wallet/"+ std::string(encodedURI); free(encodedURI); @@ -393,7 +395,7 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co if (failedToGetAuthCookie) { throw std::runtime_error(strprintf( "Could not locate RPC credentials. No authentication cookie could be found, and RPC password is not set. See -rpcpassword and -stdinrpcpass. Configuration file: (%s)", - GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string().c_str())); + GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME)).string())); } else { throw std::runtime_error("Authorization failed: Incorrect rpcuser or rpcpassword"); } @@ -541,7 +543,7 @@ static int CommandLineRPC(int argc, char *argv[]) } if (strPrint != "") { - tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint.c_str()); + tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint); } return nRet; } diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index cabea610f3..c7af7e0fc8 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -83,7 +83,7 @@ static int AppInitRawTx(int argc, char* argv[]) SetupBitcoinTxArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error); return EXIT_FAILURE; } @@ -105,7 +105,7 @@ static int AppInitRawTx(int argc, char* argv[]) "\n"; strUsage += gArgs.GetHelpMessage(); - tfm::format(std::cout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage); if (argc < 2) { tfm::format(std::cerr, "Error: too few parameters\n"); @@ -724,21 +724,21 @@ static void OutputTxJSON(const CTransaction& tx) TxToUniv(tx, uint256(), entry); std::string jsonOutput = entry.write(4); - tfm::format(std::cout, "%s\n", jsonOutput.c_str()); + tfm::format(std::cout, "%s\n", jsonOutput); } static void OutputTxHash(const CTransaction& tx) { std::string strHexHash = tx.GetHash().GetHex(); // the hex-encoded transaction hash (aka the transaction id) - tfm::format(std::cout, "%s\n", strHexHash.c_str()); + tfm::format(std::cout, "%s\n", strHexHash); } static void OutputTxHex(const CTransaction& tx) { std::string strHex = EncodeHexTx(tx); - tfm::format(std::cout, "%s\n", strHex.c_str()); + tfm::format(std::cout, "%s\n", strHex); } static void OutputTx(const CTransaction& tx) @@ -829,7 +829,7 @@ static int CommandLineRawTx(int argc, char* argv[]) } if (strPrint != "") { - tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint.c_str()); + tfm::format(nRet == 0 ? std::cout : std::cerr, "%s\n", strPrint); } return nRet; } diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index eda4f8ce78..917ecd71c5 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -36,7 +36,7 @@ static bool WalletAppInit(int argc, char* argv[]) SetupWalletToolArgs(); std::string error_message; if (!gArgs.ParseParameters(argc, argv, error_message)) { - tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message.c_str()); + tfm::format(std::cerr, "Error parsing command line arguments: %s\n", error_message); return false; } if (argc < 2 || HelpRequested(gArgs)) { @@ -48,7 +48,7 @@ static bool WalletAppInit(int argc, char* argv[]) " bitcoin-wallet [options] <command>\n\n" + gArgs.GetHelpMessage(); - tfm::format(std::cout, "%s", usage.c_str()); + tfm::format(std::cout, "%s", usage); return false; } @@ -56,7 +56,7 @@ static bool WalletAppInit(int argc, char* argv[]) LogInstance().m_print_to_console = gArgs.GetBoolArg("-printtoconsole", gArgs.GetBoolArg("-debug", false)); if (!CheckDataDirOption()) { - tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); + tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")); return false; } // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) @@ -87,7 +87,7 @@ int main(int argc, char* argv[]) for(int i = 1; i < argc; ++i) { if (!IsSwitchChar(argv[i][0])) { if (!method.empty()) { - tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method.c_str(), argv[i]); + tfm::format(std::cerr, "Error: two methods provided (%s and %s). Only one method should be provided.\n", method, argv[i]); return EXIT_FAILURE; } method = argv[i]; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index ddd6f8839c..4b5cea4849 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -12,6 +12,7 @@ #include <compat.h> #include <init.h> #include <interfaces/chain.h> +#include <node/context.h> #include <noui.h> #include <shutdown.h> #include <ui_interface.h> @@ -24,13 +25,13 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; -static void WaitForShutdown() +static void WaitForShutdown(NodeContext& node) { while (!ShutdownRequested()) { MilliSleep(200); } - Interrupt(); + Interrupt(node); } ////////////////////////////////////////////////////////////////////////////// @@ -39,8 +40,8 @@ static void WaitForShutdown() // static bool AppInit(int argc, char* argv[]) { - InitInterfaces interfaces; - interfaces.chain = interfaces::MakeChain(); + NodeContext node; + node.chain = interfaces::MakeChain(node); bool fRet = false; @@ -70,7 +71,7 @@ static bool AppInit(int argc, char* argv[]) strUsage += "\n" + gArgs.GetHelpMessage(); } - tfm::format(std::cout, "%s", strUsage.c_str()); + tfm::format(std::cout, "%s", strUsage); return true; } @@ -142,7 +143,7 @@ static bool AppInit(int argc, char* argv[]) // If locking the data directory failed, exit immediately return false; } - fRet = AppInitMain(interfaces); + fRet = AppInitMain(node); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); @@ -152,11 +153,11 @@ static bool AppInit(int argc, char* argv[]) if (!fRet) { - Interrupt(); + Interrupt(node); } else { - WaitForShutdown(); + WaitForShutdown(node); } - Shutdown(interfaces); + Shutdown(node); return fRet; } diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 7ff30ac1cd..dd4d3e97ac 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -62,7 +62,7 @@ static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint32_t nBits class CMainParams : public CChainParams { public: CMainParams() { - strNetworkID = "main"; + strNetworkID = CBaseChainParams::MAIN; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); consensus.BIP34Height = 227931; @@ -169,7 +169,7 @@ public: class CTestNetParams : public CChainParams { public: CTestNetParams() { - strNetworkID = "test"; + strNetworkID = CBaseChainParams::TESTNET; consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; @@ -254,7 +254,7 @@ public: class CRegTestParams : public CChainParams { public: explicit CRegTestParams(const ArgsManager& args) { - strNetworkID = "regtest"; + strNetworkID = CBaseChainParams::REGTEST; consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) diff --git a/src/crypto/hkdf_sha256_32.cpp b/src/crypto/hkdf_sha256_32.cpp index 9cea5995ec..e684eced37 100644 --- a/src/crypto/hkdf_sha256_32.cpp +++ b/src/crypto/hkdf_sha256_32.cpp @@ -9,7 +9,7 @@ CHKDF_HMAC_SHA256_L32::CHKDF_HMAC_SHA256_L32(const unsigned char* ikm, size_t ikmlen, const std::string& salt) { - CHMAC_SHA256((const unsigned char*)salt.c_str(), salt.size()).Write(ikm, ikmlen).Finalize(m_prk); + CHMAC_SHA256((const unsigned char*)salt.data(), salt.size()).Write(ikm, ikmlen).Finalize(m_prk); } void CHKDF_HMAC_SHA256_L32::Expand32(const std::string& info, unsigned char hash[OUTPUT_SIZE]) diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 0edcb0286d..38b5b0efc4 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -19,7 +19,7 @@ public: bool HasWalletSupport() const override {return false;} void AddWalletOptions() const override; bool ParameterInteraction() const override {return true;} - void Construct(InitInterfaces& interfaces) const override {LogPrintf("No wallet support compiled in!\n");} + void Construct(NodeContext& node) const override {LogPrintf("No wallet support compiled in!\n");} }; void DummyWalletInit::AddWalletOptions() const diff --git a/src/fs.cpp b/src/fs.cpp index 7b422b8d70..73fb3b606e 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -107,10 +107,10 @@ std::string get_filesystem_error_message(const fs::filesystem_error& e) #else // Convert from Multi Byte to utf-16 std::string mb_string(e.what()); - int size = MultiByteToWideChar(CP_ACP, 0, mb_string.c_str(), mb_string.size(), nullptr, 0); + int size = MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), nullptr, 0); std::wstring utf16_string(size, L'\0'); - MultiByteToWideChar(CP_ACP, 0, mb_string.c_str(), mb_string.size(), &*utf16_string.begin(), size); + MultiByteToWideChar(CP_ACP, 0, mb_string.data(), mb_string.size(), &*utf16_string.begin(), size); // Convert from utf-16 to utf-8 return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>().to_bytes(utf16_string); #endif diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 2c2f67b169..0437f0c7de 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -112,7 +112,7 @@ static bool multiUserAuthorized(std::string strUserPass) static const unsigned int KEY_SIZE = 32; unsigned char out[KEY_SIZE]; - CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.c_str()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.c_str()), strPass.size()).Finalize(out); + CHMAC_SHA256(reinterpret_cast<const unsigned char*>(strSalt.data()), strSalt.size()).Write(reinterpret_cast<const unsigned char*>(strPass.data()), strPass.size()).Finalize(out); std::vector<unsigned char> hexvec(out, out+KEY_SIZE); std::string strHashFromPass = HexStr(hexvec); diff --git a/src/init.cpp b/src/init.cpp index da4d322669..ef017efbb9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -29,6 +29,7 @@ #include <net_permissions.h> #include <net_processing.h> #include <netbase.h> +#include <node/context.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> @@ -84,9 +85,6 @@ static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false; // Dump addresses to banlist.dat every 15 minutes (900s) static constexpr int DUMP_BANS_INTERVAL = 60 * 15; -std::unique_ptr<CConnman> g_connman; -std::unique_ptr<PeerLogicValidation> peerLogic; -std::unique_ptr<BanMan> g_banman; #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for @@ -154,7 +152,7 @@ static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle; static boost::thread_group threadGroup; static CScheduler scheduler; -void Interrupt() +void Interrupt(NodeContext& node) { InterruptHTTPServer(); InterruptHTTPRPC(); @@ -162,15 +160,15 @@ void Interrupt() InterruptREST(); InterruptTorControl(); InterruptMapPort(); - if (g_connman) - g_connman->Interrupt(); + if (node.connman) + node.connman->Interrupt(); if (g_txindex) { g_txindex->Interrupt(); } ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Interrupt(); }); } -void Shutdown(InitInterfaces& interfaces) +void Shutdown(NodeContext& node) { LogPrintf("%s: In progress...\n", __func__); static CCriticalSection cs_Shutdown; @@ -189,15 +187,15 @@ void Shutdown(InitInterfaces& interfaces) StopREST(); StopRPC(); StopHTTPServer(); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->flush(); } StopMapPort(); // Because these depend on each-other, we make sure that neither can be // using the other before destroying them. - if (peerLogic) UnregisterValidationInterface(peerLogic.get()); - if (g_connman) g_connman->Stop(); + if (node.peer_logic) UnregisterValidationInterface(node.peer_logic.get()); + if (node.connman) node.connman->Stop(); if (g_txindex) g_txindex->Stop(); ForEachBlockFilterIndex([](BlockFilterIndex& index) { index.Stop(); }); @@ -210,9 +208,9 @@ void Shutdown(InitInterfaces& interfaces) // After the threads that potentially access these pointers have been stopped, // destruct and reset all to nullptr. - peerLogic.reset(); - g_connman.reset(); - g_banman.reset(); + node.peer_logic.reset(); + node.connman.reset(); + node.banman.reset(); g_txindex.reset(); DestroyAllBlockFilterIndexes(); @@ -261,7 +259,7 @@ void Shutdown(InitInterfaces& interfaces) } pblocktree.reset(); } - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->stop(); } @@ -280,7 +278,7 @@ void Shutdown(InitInterfaces& interfaces) } catch (const fs::filesystem_error& e) { LogPrintf("%s: Unable to remove PID file: %s\n", __func__, fsbridge::get_filesystem_error_message(e)); } - interfaces.chain_clients.clear(); + node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); GetMainSignals().UnregisterWithMempoolSignals(mempool); @@ -943,7 +941,7 @@ bool AppInitParameterInteraction() } if (!fs::is_directory(GetBlocksDir())) { - return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist.").translated, gArgs.GetArg("-blocksdir", "").c_str())); + return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist.").translated, gArgs.GetArg("-blocksdir", ""))); } // parse and validate enabled filter types @@ -1207,7 +1205,7 @@ bool AppInitLockDataDirectory() return true; } -bool AppInitMain(InitInterfaces& interfaces) +bool AppInitMain(NodeContext& node) { const CChainParams& chainparams = Params(); // ********************************************************* Step 4a: application initialization @@ -1275,16 +1273,16 @@ bool AppInitMain(InitInterfaces& interfaces) // according to -wallet and -disablewallet options. This only constructs // the interfaces, it doesn't load wallet data. Wallets actually get loaded // when load() and start() interface methods are called below. - g_wallet_init_interface.Construct(interfaces); + g_wallet_init_interface.Construct(node); /* Register RPC commands regardless of -server setting so they will be * available in the GUI RPC console even if external calls are disabled. */ RegisterAllCoreRPCCommands(tableRPC); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->registerRpcs(); } - g_rpc_interfaces = &interfaces; + g_rpc_node = &node; #if ENABLE_ZMQ RegisterZMQRPCCommands(tableRPC); #endif @@ -1302,7 +1300,7 @@ bool AppInitMain(InitInterfaces& interfaces) } // ********************************************************* Step 5: verify wallet database integrity - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { if (!client->verify()) { return false; } @@ -1314,13 +1312,13 @@ bool AppInitMain(InitInterfaces& interfaces) // is not yet setup and may end up being set up twice if we // need to reindex later. - assert(!g_banman); - g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); - assert(!g_connman); - g_connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); + assert(!node.banman); + node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME)); + assert(!node.connman); + node.connman = std::unique_ptr<CConnman>(new CConnman(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()))); - peerLogic.reset(new PeerLogicValidation(g_connman.get(), g_banman.get(), scheduler)); - RegisterValidationInterface(peerLogic.get()); + node.peer_logic.reset(new PeerLogicValidation(node.connman.get(), node.banman.get(), scheduler)); + RegisterValidationInterface(node.peer_logic.get()); // sanitize comments per BIP-0014, format user agent and check total size std::vector<std::string> uacomments; @@ -1661,7 +1659,7 @@ bool AppInitMain(InitInterfaces& interfaces) } // ********************************************************* Step 9: load wallet - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { if (!client->load()) { return false; } @@ -1765,8 +1763,8 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.nMaxFeeler = 1; connOptions.nBestHeight = chain_active_height; connOptions.uiInterface = &uiInterface; - connOptions.m_banman = g_banman.get(); - connOptions.m_msgproc = peerLogic.get(); + connOptions.m_banman = node.banman.get(); + connOptions.m_msgproc = node.peer_logic.get(); connOptions.nSendBufferMaxSize = 1000*gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER); connOptions.nReceiveFloodSize = 1000*gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER); connOptions.m_added_nodes = gArgs.GetArgs("-addnode"); @@ -1806,7 +1804,7 @@ bool AppInitMain(InitInterfaces& interfaces) connOptions.m_specified_outgoing = connect; } } - if (!g_connman->Start(scheduler, connOptions)) { + if (!node.connman->Start(scheduler, connOptions)) { return false; } @@ -1815,12 +1813,13 @@ bool AppInitMain(InitInterfaces& interfaces) SetRPCWarmupFinished(); uiInterface.InitMessage(_("Done loading").translated); - for (const auto& client : interfaces.chain_clients) { + for (const auto& client : node.chain_clients) { client->start(scheduler); } - scheduler.scheduleEvery([]{ - g_banman->DumpBanlist(); + BanMan* banman = node.banman.get(); + scheduler.scheduleEvery([banman]{ + banman->DumpBanlist(); }, DUMP_BANS_INTERVAL * 1000); return true; diff --git a/src/init.h b/src/init.h index 1c59ca069e..ca52dadf08 100644 --- a/src/init.h +++ b/src/init.h @@ -10,26 +10,14 @@ #include <string> #include <util/system.h> -namespace interfaces { -class Chain; -class ChainClient; -} // namespace interfaces - -//! Pointers to interfaces used during init and destroyed on shutdown. -struct InitInterfaces -{ - std::unique_ptr<interfaces::Chain> chain; - std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; -}; - -namespace boost -{ +struct NodeContext; +namespace boost { class thread_group; } // namespace boost /** Interrupt threads */ -void Interrupt(); -void Shutdown(InitInterfaces& interfaces); +void Interrupt(NodeContext& node); +void Shutdown(NodeContext& node); //!Initialize the logging infrastructure void InitLogging(); //!Parameter interaction: change current parameters depending on various rules @@ -63,7 +51,7 @@ bool AppInitLockDataDirectory(); * @note This should only be done after daemonization. Call Shutdown() if this function fails. * @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called. */ -bool AppInitMain(InitInterfaces& interfaces); +bool AppInitMain(NodeContext& node); /** * Setup the arguments for gArgs diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index b2c20573fb..23099a7799 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -11,6 +11,7 @@ #include <net.h> #include <net_processing.h> #include <node/coin.h> +#include <node/context.h> #include <node/transaction.h> #include <policy/fees.h> #include <policy/policy.h> @@ -238,6 +239,7 @@ public: class ChainImpl : public Chain { public: + explicit ChainImpl(NodeContext& node) : m_node(node) {} std::unique_ptr<Chain::Lock> lock(bool try_lock) override { auto result = MakeUnique<LockImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); @@ -286,7 +288,7 @@ public: } bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override { - const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures // that Chain clients do not need to know about. @@ -378,9 +380,10 @@ public: notifications.TransactionAddedToMempool(entry.GetSharedTx()); } } + NodeContext& m_node; }; } // namespace -std::unique_ptr<Chain> MakeChain() { return MakeUnique<ChainImpl>(); } +std::unique_ptr<Chain> MakeChain(NodeContext& node) { return MakeUnique<ChainImpl>(node); } } // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 73a78e21fb..3fe12088c5 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -24,6 +24,7 @@ class uint256; enum class RBFTransactionState; struct CBlockLocator; struct FeeCalculation; +struct NodeContext; namespace interfaces { @@ -291,7 +292,7 @@ public: }; //! Return implementation of Chain interface. -std::unique_ptr<Chain> MakeChain(); +std::unique_ptr<Chain> MakeChain(NodeContext& node); //! Return implementation of ChainClient interface for a wallet client. This //! function will be undefined in builds where ENABLE_WALLET is false. diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 6577895d50..1877c92178 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -16,6 +16,7 @@ #include <net_processing.h> #include <netaddress.h> #include <netbase.h> +#include <node/context.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/settings.h> @@ -52,7 +53,6 @@ namespace { class NodeImpl : public Node { public: - NodeImpl() { m_interfaces.chain = MakeChain(); } void initError(const std::string& message) override { InitError(message); } bool parseParameters(int argc, const char* const argv[], std::string& error) override { @@ -75,11 +75,15 @@ public: return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() && AppInitLockDataDirectory(); } - bool appInitMain() override { return AppInitMain(m_interfaces); } + bool appInitMain() override + { + m_context.chain = MakeChain(m_context); + return AppInitMain(m_context); + } void appShutdown() override { - Interrupt(); - Shutdown(m_interfaces); + Interrupt(m_context); + Shutdown(m_context); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } @@ -96,15 +100,15 @@ public: bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { - return g_connman ? g_connman->GetNodeCount(flags) : 0; + return m_context.connman ? m_context.connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats& stats) override { stats.clear(); - if (g_connman) { + if (m_context.connman) { std::vector<CNodeStats> stats_temp; - g_connman->GetNodeStats(stats_temp); + m_context.connman->GetNodeStats(stats_temp); stats.reserve(stats_temp.size()); for (auto& node_stats_temp : stats_temp) { @@ -125,44 +129,44 @@ public: } bool getBanned(banmap_t& banmap) override { - if (g_banman) { - g_banman->GetBanned(banmap); + if (m_context.banman) { + m_context.banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr& net_addr, BanReason reason, int64_t ban_time_offset) override { - if (g_banman) { - g_banman->Ban(net_addr, reason, ban_time_offset); + if (m_context.banman) { + m_context.banman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet& ip) override { - if (g_banman) { - g_banman->Unban(ip); + if (m_context.banman) { + m_context.banman->Unban(ip); return true; } return false; } bool disconnect(const CNetAddr& net_addr) override { - if (g_connman) { - return g_connman->DisconnectNode(net_addr); + if (m_context.connman) { + return m_context.connman->DisconnectNode(net_addr); } return false; } bool disconnect(NodeId id) override { - if (g_connman) { - return g_connman->DisconnectNode(id); + if (m_context.connman) { + return m_context.connman->DisconnectNode(id); } return false; } - int64_t getTotalBytesRecv() override { return g_connman ? g_connman->GetTotalBytesRecv() : 0; } - int64_t getTotalBytesSent() override { return g_connman ? g_connman->GetTotalBytesSent() : 0; } + int64_t getTotalBytesRecv() override { return m_context.connman ? m_context.connman->GetTotalBytesRecv() : 0; } + int64_t getTotalBytesSent() override { return m_context.connman ? m_context.connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return ::mempool.size(); } size_t getMempoolDynamicUsage() override { return ::mempool.DynamicMemoryUsage(); } bool getHeaderTip(int& height, int64_t& block_time) override @@ -202,11 +206,11 @@ public: bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override { - if (g_connman) { - g_connman->SetNetworkActive(active); + if (m_context.connman) { + m_context.connman->SetNetworkActive(active); } } - bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } + bool getNetworkActive() override { return m_context.connman && m_context.connman->GetNetworkActive(); } CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override { FeeCalculation fee_calc; @@ -255,12 +259,12 @@ public: } std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::vector<std::string>& warnings) override { - return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warnings)); + return MakeWallet(LoadWallet(*m_context.chain, name, error, warnings)); } WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::unique_ptr<Wallet>& result) override { std::shared_ptr<CWallet> wallet; - WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warnings, wallet); + WalletCreationStatus status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, error, warnings, wallet); result = MakeWallet(wallet); return status; } @@ -315,7 +319,8 @@ public: /* verification progress is unused when a header was received */ 0); })); } - InitInterfaces m_interfaces; + NodeContext* context() override { return &m_context; } + NodeContext m_context; }; } // namespace diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 4ee467014c..c29037f2e3 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -28,6 +28,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +struct NodeContext; enum class WalletCreationStatus; namespace interfaces { @@ -254,6 +255,9 @@ public: using NotifyHeaderTipFn = std::function<void(bool initial_download, int height, int64_t block_time, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; + + //! Return pointer to internal chain interface, useful for testing. + virtual NodeContext* context() { return nullptr; } }; //! Return implementation of Node interface. diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 9b0a8b64c9..b6ede08b14 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -46,7 +46,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co result.txout_is_mine.emplace_back(wallet.IsMine(txout)); result.txout_address.emplace_back(); result.txout_address_is_mine.emplace_back(ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ? - IsMine(wallet, result.txout_address.back()) : + wallet.IsMine(result.txout_address.back()) : ISMINE_NO); } result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); @@ -117,10 +117,17 @@ public: std::string error; return m_wallet->GetNewDestination(type, label, dest, error); } - bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetPubKey(address, pub_key); } - bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetKey(address, key); } - bool isSpendable(const CTxDestination& dest) override { return IsMine(*m_wallet, dest) & ISMINE_SPENDABLE; } - bool haveWatchOnly() override { return m_wallet->HaveWatchOnly(); }; + bool getPubKey(const CKeyID& address, CPubKey& pub_key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetPubKey(address, pub_key); } + bool getPrivKey(const CKeyID& address, CKey& key) override { return m_wallet->GetLegacyScriptPubKeyMan()->GetKey(address, key); } + bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } + bool haveWatchOnly() override + { + auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); + if (spk_man) { + return spk_man->HaveWatchOnly(); + } + return false; + }; bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::string& purpose) override { return m_wallet->SetAddressBook(dest, name, purpose); @@ -143,7 +150,7 @@ public: *name = it->second.name; } if (is_mine) { - *is_mine = IsMine(*m_wallet, dest); + *is_mine = m_wallet->IsMine(dest); } if (purpose) { *purpose = it->second.purpose; @@ -155,11 +162,11 @@ public: LOCK(m_wallet->cs_wallet); std::vector<WalletAddress> result; for (const auto& item : m_wallet->mapAddressBook) { - result.emplace_back(item.first, IsMine(*m_wallet, item.first), item.second.name, item.second.purpose); + result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.name, item.second.purpose); } return result; } - void learnRelatedScripts(const CPubKey& key, OutputType type) override { m_wallet->LearnRelatedScripts(key, type); } + void learnRelatedScripts(const CPubKey& key, OutputType type) override { m_wallet->GetLegacyScriptPubKeyMan()->LearnRelatedScripts(key, type); } bool addDestData(const CTxDestination& dest, const std::string& key, const std::string& value) override { LOCK(m_wallet->cs_wallet); @@ -342,7 +349,7 @@ public: result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.immature_balance = bal.m_mine_immature; - result.have_watch_only = m_wallet->HaveWatchOnly(); + result.have_watch_only = haveWatchOnly(); if (result.have_watch_only) { result.watch_only_balance = bal.m_watchonly_trusted; result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; @@ -489,7 +496,11 @@ public: : m_chain(chain), m_wallet_filenames(std::move(wallet_filenames)) { } - void registerRpcs() override { return RegisterWalletRPCCommands(m_chain, m_rpc_handlers); } + void registerRpcs() override + { + g_rpc_chain = &m_chain; + return RegisterWalletRPCCommands(m_chain, m_rpc_handlers); + } bool verify() override { return VerifyWallets(m_chain, m_wallet_filenames); } bool load() override { return LoadWallets(m_chain, m_wallet_filenames); } void start(CScheduler& scheduler) override { return StartWallets(scheduler); } diff --git a/src/net.cpp b/src/net.cpp index 7ae88b47a0..674f2ecf24 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1474,7 +1474,7 @@ static void ThreadMapPort() if (externalIPAddress[0]) { CNetAddr resolved; if (LookupHost(externalIPAddress, resolved, false)) { - LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString().c_str()); + LogPrintf("UPnP: ExternalIPAddress = %s\n", resolved.ToString()); AddLocal(resolved, LOCAL_UPNP); } } else { @@ -2710,7 +2710,7 @@ void CConnman::PushMessage(CNode* pnode, CSerializedNetMsg&& msg) { size_t nMessageSize = msg.data.size(); size_t nTotalSize = nMessageSize + CMessageHeader::HEADER_SIZE; - LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.command.c_str()), nMessageSize, pnode->GetId()); + LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n", SanitizeString(msg.command), nMessageSize, pnode->GetId()); std::vector<unsigned char> serializedHeader; serializedHeader.reserve(CMessageHeader::HEADER_SIZE); @@ -480,8 +480,6 @@ private: friend struct CConnmanTest; }; -extern std::unique_ptr<CConnman> g_connman; -extern std::unique_ptr<BanMan> g_banman; void Discover(); void StartMapPort(); void InterruptMapPort(); diff --git a/src/net_processing.cpp b/src/net_processing.cpp index fd31c962c2..836da0be70 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1860,7 +1860,7 @@ void static ProcessOrphanTx(CConnman* connman, std::set<uint256>& orphan_work_se } } -bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, const std::atomic<bool>& interruptMsgProc) +bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, int64_t nTimeReceived, const CChainParams& chainparams, CConnman* connman, BanMan* banman, const std::atomic<bool>& interruptMsgProc) { LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n", SanitizeString(strCommand), vRecv.size(), pfrom->GetId()); if (gArgs.IsArgSet("-dropmessagestest") && GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) @@ -2136,7 +2136,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) addr.nTime = nNow - 5 * 24 * 60 * 60; pfrom->AddAddressKnown(addr); - if (g_banman->IsBanned(addr)) continue; // Do not process banned addresses beyond remembering we received them + if (banman->IsBanned(addr)) continue; // Do not process banned addresses beyond remembering we received them bool fReachable = IsReachable(addr); if (addr.nTime > nSince && !pfrom->fGetAddr && vAddr.size() <= 10 && addr.IsRoutable()) { @@ -2772,7 +2772,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr } // cs_main if (fProcessBLOCKTXN) - return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, interruptMsgProc); + return ProcessMessage(pfrom, NetMsgType::BLOCKTXN, blockTxnMsg, nTimeReceived, chainparams, connman, banman, interruptMsgProc); if (fRevertToHeaderProcessing) { // Headers received from HB compact block peers are permitted to be @@ -2990,7 +2990,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr std::vector<CAddress> vAddr = connman->GetAddresses(); FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) { - if (!g_banman->IsBanned(addr)) { + if (!banman->IsBanned(addr)) { pfrom->PushAddress(addr, insecure_rand); } } @@ -3310,7 +3310,7 @@ bool PeerLogicValidation::ProcessMessages(CNode* pfrom, std::atomic<bool>& inter bool fRet = false; try { - fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.m_time, chainparams, connman, interruptMsgProc); + fRet = ProcessMessage(pfrom, strCommand, vRecv, msg.m_time, chainparams, connman, m_banman, interruptMsgProc); if (interruptMsgProc) return false; if (!pfrom->vRecvGetData.empty()) diff --git a/src/node/context.cpp b/src/node/context.cpp new file mode 100644 index 0000000000..26a01420c8 --- /dev/null +++ b/src/node/context.cpp @@ -0,0 +1,13 @@ +// 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 <node/context.h> + +#include <banman.h> +#include <interfaces/chain.h> +#include <net.h> +#include <net_processing.h> + +NodeContext::NodeContext() {} +NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h new file mode 100644 index 0000000000..2b124af4db --- /dev/null +++ b/src/node/context.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef BITCOIN_NODE_CONTEXT_H +#define BITCOIN_NODE_CONTEXT_H + +#include <memory> +#include <vector> + +class BanMan; +class CConnman; +class PeerLogicValidation; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! NodeContext struct containing references to chain state and connection +//! state. +//! +//! This is used by init, rpc, and test code to pass object references around +//! without needing to declare the same variables and parameters repeatedly, or +//! to use globals. More variables could be added to this struct (particularly +//! references to validation and mempool objects) to eliminate use of globals +//! and make code more modular and testable. The struct isn't intended to have +//! any member functions. It should just be a collection of references that can +//! be used without pulling in unwanted dependencies or functionality. +struct NodeContext +{ + std::unique_ptr<CConnman> connman; + std::unique_ptr<PeerLogicValidation> peer_logic; + std::unique_ptr<BanMan> banman; + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; + + //! Declare default constructor and destructor that are not inline, so code + //! instantiating the NodeContext struct doesn't need to #include class + //! definitions for all the unique_ptr members. + NodeContext(); + ~NodeContext(); +}; + +#endif // BITCOIN_NODE_CONTEXT_H diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 7783671a6c..ba4f3c5370 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -6,6 +6,7 @@ #include <consensus/validation.h> #include <net.h> #include <net_processing.h> +#include <node/context.h> #include <util/validation.h> #include <validation.h> #include <validationinterface.h> @@ -13,12 +14,12 @@ #include <future> -TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) +TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. - // g_connman is assigned both before chain clients and before RPC server is accepting calls, - // and reset after chain clients and RPC sever are stopped. g_connman should never be null here. - assert(g_connman); + // node.connman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. node.connman should never be null here. + assert(node.connman); std::promise<void> promise; uint256 hashTx = tx->GetHash(); bool callback_set = false; @@ -79,7 +80,7 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err } if (relay) { - RelayTransaction(hashTx, *g_connman); + RelayTransaction(hashTx, *node.connman); } return TransactionError::OK; diff --git a/src/node/transaction.h b/src/node/transaction.h index a3e56544a7..35873d8376 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -9,6 +9,8 @@ #include <primitives/transaction.h> #include <util/error.h> +struct NodeContext; + /** * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. * @@ -18,6 +20,7 @@ * NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid * deadlock. * + * @param[in] node reference to node context * @param[in] tx the transaction to broadcast * @param[out] &err_string reference to std::string to fill with error string if available * @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee) @@ -25,6 +28,6 @@ * @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); +NODISCARD TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); #endif // BITCOIN_NODE_TRANSACTION_H diff --git a/src/noui.cpp b/src/noui.cpp index 14d6183d24..11c8f1e13d 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -45,7 +45,7 @@ bool noui_ThreadSafeMessageBox(const std::string& message, const std::string& ca if (!fSecure) { LogPrintf("%s%s\n", strCaption, message); } - tfm::format(std::cerr, "%s%s\n", strCaption.c_str(), message.c_str()); + tfm::format(std::cerr, "%s%s\n", strCaption, message); return false; } @@ -96,4 +96,4 @@ void noui_reconnect() noui_ThreadSafeQuestionConn.disconnect(); noui_InitMessageConn.disconnect(); noui_connect(); -}
\ No newline at end of file +} diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 8154bf105e..a66e4464db 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -517,7 +517,7 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo uint256 hash = entry.GetTx().GetHash(); if (mapMemPoolTxs.count(hash)) { LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy error mempool tx %s already being tracked\n", - hash.ToString().c_str()); + hash.ToString()); return; } diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 4fe440a679..8b32b70d1e 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -51,11 +51,10 @@ void EditAddressAndSubmit( * In each case, verify the resulting state of the address book and optionally * the warning message presented to the user. */ -void TestAddAddressesToSendBook() +void TestAddAddressesToSendBook(interfaces::Node& node) { TestChain100Setup test; - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); @@ -101,10 +100,9 @@ void TestAddAddressesToSendBook() // Initialize relevant QT models. std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); - auto node = interfaces::MakeNode(); - OptionsModel optionsModel(*node); + OptionsModel optionsModel(node); AddWallet(wallet); - WalletModel walletModel(std::move(node->getWallets()[0]), *node, platformStyle.get(), &optionsModel); + WalletModel walletModel(interfaces::MakeWallet(wallet), node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); EditAddressDialog editAddressDialog(EditAddressDialog::NewSendingAddress); editAddressDialog.setModel(walletModel.getAddressTableModel()); @@ -150,5 +148,5 @@ void AddressBookTests::addressBookTests() return; } #endif - TestAddAddressesToSendBook(); + TestAddAddressesToSendBook(m_node); } diff --git a/src/qt/test/addressbooktests.h b/src/qt/test/addressbooktests.h index beeb9e76a9..9944750ec8 100644 --- a/src/qt/test/addressbooktests.h +++ b/src/qt/test/addressbooktests.h @@ -4,8 +4,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class AddressBookTests : public QObject { +public: + AddressBookTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 3c2ffa6c00..1772de4c1b 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -41,7 +41,7 @@ void RPCNestedTests::rpcNestedTests() std::string result; std::string result2; std::string filtered; - auto node = interfaces::MakeNode(); + interfaces::Node* node = &m_node; RPCConsole::RPCExecuteCommandLine(*node, result, "getblockchaininfo()[chain]", &filtered); //simple result filtering with path QVERIFY(result=="main"); QVERIFY(filtered == "getblockchaininfo()[chain]"); diff --git a/src/qt/test/rpcnestedtests.h b/src/qt/test/rpcnestedtests.h index 97143ff78a..8789fe8373 100644 --- a/src/qt/test/rpcnestedtests.h +++ b/src/qt/test/rpcnestedtests.h @@ -8,8 +8,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class RPCNestedTests : public QObject { +public: + RPCNestedTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index f272627f96..e6870cf1be 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) BasicTestingSetup dummy{CBaseChainParams::REGTEST}; } - auto node = interfaces::MakeNode(); + std::unique_ptr<interfaces::Node> node = interfaces::MakeNode(); bool fInvalid = false; @@ -76,7 +76,7 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test1) != 0) { fInvalid = true; } - RPCNestedTests test3; + RPCNestedTests test3(*node); if (QTest::qExec(&test3) != 0) { fInvalid = true; } @@ -85,11 +85,11 @@ int main(int argc, char *argv[]) fInvalid = true; } #ifdef ENABLE_WALLET - WalletTests test5; + WalletTests test5(*node); if (QTest::qExec(&test5) != 0) { fInvalid = true; } - AddressBookTests test6; + AddressBookTests test6(*node); if (QTest::qExec(&test6) != 0) { fInvalid = true; } diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index eea874c0d4..881653cdac 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -126,21 +126,23 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st // QT_QPA_PLATFORM=xcb src/qt/test/test_bitcoin-qt # Linux // QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt # Windows // QT_QPA_PLATFORM=cocoa src/qt/test/test_bitcoin-qt # macOS -void TestGUI() +void TestGUI(interfaces::Node& node) { // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } - auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + node.context()->connman = std::move(test.m_node.connman); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); { + auto spk_man = wallet->GetLegacyScriptPubKeyMan(); LOCK(wallet->cs_wallet); + AssertLockHeld(spk_man->cs_wallet); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); - wallet->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); + spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { auto locked_chain = wallet->chain().lock(); @@ -159,10 +161,9 @@ void TestGUI() std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other")); SendCoinsDialog sendCoinsDialog(platformStyle.get()); TransactionView transactionView(platformStyle.get()); - auto node = interfaces::MakeNode(); - OptionsModel optionsModel(*node); + OptionsModel optionsModel(node); AddWallet(wallet); - WalletModel walletModel(std::move(node->getWallets().back()), *node, platformStyle.get(), &optionsModel); + WalletModel walletModel(interfaces::MakeWallet(wallet), node, platformStyle.get(), &optionsModel); RemoveWallet(wallet); sendCoinsDialog.setModel(&walletModel); transactionView.setModel(&walletModel); @@ -260,5 +261,5 @@ void WalletTests::walletTests() return; } #endif - TestGUI(); + TestGUI(m_node); } diff --git a/src/qt/test/wallettests.h b/src/qt/test/wallettests.h index 342f7916c3..0a7b57a678 100644 --- a/src/qt/test/wallettests.h +++ b/src/qt/test/wallettests.h @@ -4,8 +4,16 @@ #include <QObject> #include <QTest> +namespace interfaces { +class Node; +} // namespace interfaces + class WalletTests : public QObject { + public: + WalletTests(interfaces::Node& node) : m_node(node) {} + interfaces::Node& m_node; + Q_OBJECT private Q_SLOTS: diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index fac6bcd60d..747113fbb4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2289,3 +2289,5 @@ void RegisterBlockchainRPCCommands(CRPCTable &t) for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) t.appendCommand(commands[vcidx].name, &commands[vcidx]); } + +NodeContext* g_rpc_node = nullptr; diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index ff461fbcbc..8a1264f824 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -17,6 +17,7 @@ class CBlock; class CBlockIndex; class CTxMemPool; class UniValue; +struct NodeContext; static constexpr int NUM_GETBLOCKSTATS_PERCENTILES = 5; @@ -46,4 +47,9 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight); +//! Pointer to node state that needs to be declared as a global to be accessible +//! RPC methods. Due to limitations of the RPC framework, there's currently no +//! direct way to pass in state to RPC methods without globals. +extern NodeContext* g_rpc_node; + #endif diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 07c2958635..bfa3e35d96 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -13,6 +13,7 @@ #include <key_io.h> #include <miner.h> #include <net.h> +#include <node/context.h> #include <policy/fees.h> #include <pow.h> #include <rpc/blockchain.h> @@ -424,10 +425,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) if (strMode != "template") throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid mode"); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - if (g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) + if (g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL) == 0) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, PACKAGE_NAME " is not connected!"); if (::ChainstateActive().IsInitialBlockDownload()) diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index 7b1507e4dc..f443f37c6d 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -11,7 +11,9 @@ #include <net_processing.h> #include <net_permissions.h> #include <netbase.h> +#include <node/context.h> #include <policy/settings.h> +#include <rpc/blockchain.h> #include <rpc/protocol.h> #include <rpc/util.h> #include <sync.h> @@ -38,10 +40,10 @@ static UniValue getconnectioncount(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - return (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL); + return (int)g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL); } static UniValue ping(const JSONRPCRequest& request) @@ -58,11 +60,11 @@ static UniValue ping(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); // Request that each node send a ping during next message processing pass - g_connman->ForEachNode([](CNode* pnode) { + g_rpc_node->connman->ForEachNode([](CNode* pnode) { pnode->fPingQueued = true; }); return NullUniValue; @@ -131,11 +133,11 @@ static UniValue getpeerinfo(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); std::vector<CNodeStats> vstats; - g_connman->GetNodeStats(vstats); + g_rpc_node->connman->GetNodeStats(vstats); UniValue ret(UniValue::VARR); @@ -234,7 +236,7 @@ static UniValue addnode(const JSONRPCRequest& request) }, }.ToString()); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); std::string strNode = request.params[0].get_str(); @@ -242,18 +244,18 @@ static UniValue addnode(const JSONRPCRequest& request) if (strCommand == "onetry") { CAddress addr; - g_connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), false, false, true); + g_rpc_node->connman->OpenNetworkConnection(addr, false, nullptr, strNode.c_str(), false, false, true); return NullUniValue; } if (strCommand == "add") { - if(!g_connman->AddNode(strNode)) + if(!g_rpc_node->connman->AddNode(strNode)) throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: Node already added"); } else if(strCommand == "remove") { - if(!g_connman->RemoveAddedNode(strNode)) + if(!g_rpc_node->connman->RemoveAddedNode(strNode)) throw JSONRPCError(RPC_CLIENT_NODE_NOT_ADDED, "Error: Node has not been added."); } @@ -279,7 +281,7 @@ static UniValue disconnectnode(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); bool success; @@ -288,11 +290,11 @@ static UniValue disconnectnode(const JSONRPCRequest& request) if (!address_arg.isNull() && id_arg.isNull()) { /* handle disconnect-by-address */ - success = g_connman->DisconnectNode(address_arg.get_str()); + success = g_rpc_node->connman->DisconnectNode(address_arg.get_str()); } else if (!id_arg.isNull() && (address_arg.isNull() || (address_arg.isStr() && address_arg.get_str().empty()))) { /* handle disconnect-by-id */ NodeId nodeid = (NodeId) id_arg.get_int64(); - success = g_connman->DisconnectNode(nodeid); + success = g_rpc_node->connman->DisconnectNode(nodeid); } else { throw JSONRPCError(RPC_INVALID_PARAMS, "Only one of address and nodeid should be provided."); } @@ -333,10 +335,10 @@ static UniValue getaddednodeinfo(const JSONRPCRequest& request) }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - std::vector<AddedNodeInfo> vInfo = g_connman->GetAddedNodeInfo(); + std::vector<AddedNodeInfo> vInfo = g_rpc_node->connman->GetAddedNodeInfo(); if (!request.params[0].isNull()) { bool found = false; @@ -399,21 +401,21 @@ static UniValue getnettotals(const JSONRPCRequest& request) + HelpExampleRpc("getnettotals", "") }, }.Check(request); - if(!g_connman) + if(!g_rpc_node->connman) throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); UniValue obj(UniValue::VOBJ); - obj.pushKV("totalbytesrecv", g_connman->GetTotalBytesRecv()); - obj.pushKV("totalbytessent", g_connman->GetTotalBytesSent()); + obj.pushKV("totalbytesrecv", g_rpc_node->connman->GetTotalBytesRecv()); + obj.pushKV("totalbytessent", g_rpc_node->connman->GetTotalBytesSent()); obj.pushKV("timemillis", GetTimeMillis()); UniValue outboundLimit(UniValue::VOBJ); - outboundLimit.pushKV("timeframe", g_connman->GetMaxOutboundTimeframe()); - outboundLimit.pushKV("target", g_connman->GetMaxOutboundTarget()); - outboundLimit.pushKV("target_reached", g_connman->OutboundTargetReached(false)); - outboundLimit.pushKV("serve_historical_blocks", !g_connman->OutboundTargetReached(true)); - outboundLimit.pushKV("bytes_left_in_cycle", g_connman->GetOutboundTargetBytesLeft()); - outboundLimit.pushKV("time_left_in_cycle", g_connman->GetMaxOutboundTimeLeftInCycle()); + outboundLimit.pushKV("timeframe", g_rpc_node->connman->GetMaxOutboundTimeframe()); + outboundLimit.pushKV("target", g_rpc_node->connman->GetMaxOutboundTarget()); + outboundLimit.pushKV("target_reached", g_rpc_node->connman->OutboundTargetReached(false)); + outboundLimit.pushKV("serve_historical_blocks", !g_rpc_node->connman->OutboundTargetReached(true)); + outboundLimit.pushKV("bytes_left_in_cycle", g_rpc_node->connman->GetOutboundTargetBytesLeft()); + outboundLimit.pushKV("time_left_in_cycle", g_rpc_node->connman->GetMaxOutboundTimeLeftInCycle()); obj.pushKV("uploadtarget", outboundLimit); return obj; } @@ -492,16 +494,16 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request) obj.pushKV("version", CLIENT_VERSION); obj.pushKV("subversion", strSubVersion); obj.pushKV("protocolversion",PROTOCOL_VERSION); - if (g_connman) { - ServiceFlags services = g_connman->GetLocalServices(); + if (g_rpc_node->connman) { + ServiceFlags services = g_rpc_node->connman->GetLocalServices(); obj.pushKV("localservices", strprintf("%016x", services)); obj.pushKV("localservicesnames", GetServicesNames(services)); } obj.pushKV("localrelay", g_relay_txes); obj.pushKV("timeoffset", GetTimeOffset()); - if (g_connman) { - obj.pushKV("networkactive", g_connman->GetNetworkActive()); - obj.pushKV("connections", (int)g_connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); + if (g_rpc_node->connman) { + obj.pushKV("networkactive", g_rpc_node->connman->GetNetworkActive()); + obj.pushKV("connections", (int)g_rpc_node->connman->GetNodeCount(CConnman::CONNECTIONS_ALL)); } obj.pushKV("networks", GetNetworksInfo()); obj.pushKV("relayfee", ValueFromAmount(::minRelayTxFee.GetFeePerK())); @@ -546,7 +548,7 @@ static UniValue setban(const JSONRPCRequest& request) if (request.fHelp || !help.IsValidNumArgs(request.params.size()) || (strCommand != "add" && strCommand != "remove")) { throw std::runtime_error(help.ToString()); } - if (!g_banman) { + if (!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } @@ -570,7 +572,7 @@ static UniValue setban(const JSONRPCRequest& request) if (strCommand == "add") { - if (isSubnet ? g_banman->IsBanned(subNet) : g_banman->IsBanned(netAddr)) { + if (isSubnet ? g_rpc_node->banman->IsBanned(subNet) : g_rpc_node->banman->IsBanned(netAddr)) { throw JSONRPCError(RPC_CLIENT_NODE_ALREADY_ADDED, "Error: IP/Subnet already banned"); } @@ -583,20 +585,20 @@ static UniValue setban(const JSONRPCRequest& request) absolute = true; if (isSubnet) { - g_banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); - if (g_connman) { - g_connman->DisconnectNode(subNet); + g_rpc_node->banman->Ban(subNet, BanReasonManuallyAdded, banTime, absolute); + if (g_rpc_node->connman) { + g_rpc_node->connman->DisconnectNode(subNet); } } else { - g_banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); - if (g_connman) { - g_connman->DisconnectNode(netAddr); + g_rpc_node->banman->Ban(netAddr, BanReasonManuallyAdded, banTime, absolute); + if (g_rpc_node->connman) { + g_rpc_node->connman->DisconnectNode(netAddr); } } } else if(strCommand == "remove") { - if (!( isSubnet ? g_banman->Unban(subNet) : g_banman->Unban(netAddr) )) { + if (!( isSubnet ? g_rpc_node->banman->Unban(subNet) : g_rpc_node->banman->Unban(netAddr) )) { throw JSONRPCError(RPC_CLIENT_INVALID_IP_OR_SUBNET, "Error: Unban failed. Requested address/subnet was not previously banned."); } } @@ -615,12 +617,12 @@ static UniValue listbanned(const JSONRPCRequest& request) }, }.Check(request); - if(!g_banman) { + if(!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } banmap_t banMap; - g_banman->GetBanned(banMap); + g_rpc_node->banman->GetBanned(banMap); UniValue bannedAddresses(UniValue::VARR); for (const auto& entry : banMap) @@ -649,11 +651,11 @@ static UniValue clearbanned(const JSONRPCRequest& request) + HelpExampleRpc("clearbanned", "") }, }.Check(request); - if (!g_banman) { + if (!g_rpc_node->banman) { throw JSONRPCError(RPC_DATABASE_ERROR, "Error: Ban database not loaded"); } - g_banman->ClearBanned(); + g_rpc_node->banman->ClearBanned(); return NullUniValue; } @@ -669,13 +671,13 @@ static UniValue setnetworkactive(const JSONRPCRequest& request) RPCExamples{""}, }.Check(request); - if (!g_connman) { + if (!g_rpc_node->connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } - g_connman->SetNetworkActive(request.params[0].get_bool()); + g_rpc_node->connman->SetNetworkActive(request.params[0].get_bool()); - return g_connman->GetNetworkActive(); + return g_rpc_node->connman->GetNetworkActive(); } static UniValue getnodeaddresses(const JSONRPCRequest& request) @@ -701,7 +703,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) + HelpExampleRpc("getnodeaddresses", "8") }, }.Check(request); - if (!g_connman) { + if (!g_rpc_node->connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } @@ -713,7 +715,7 @@ static UniValue getnodeaddresses(const JSONRPCRequest& request) } } // returns a shuffled list of CAddress - std::vector<CAddress> vAddr = g_connman->GetAddresses(); + std::vector<CAddress> vAddr = g_rpc_node->connman->GetAddresses(); UniValue ret(UniValue::VARR); int address_return_count = std::min<int>(count, vAddr.size()); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index cdcf0c9971..74cd46080b 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -11,6 +11,7 @@ #include <key_io.h> #include <merkleblock.h> #include <node/coin.h> +#include <node/context.h> #include <node/psbt.h> #include <node/transaction.h> #include <policy/policy.h> @@ -18,6 +19,7 @@ #include <primitives/transaction.h> #include <psbt.h> #include <random.h> +#include <rpc/blockchain.h> #include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> @@ -817,7 +819,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) std::string err_string; AssertLockNotHeld(cs_main); - const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); + const TransactionError err = BroadcastTransaction(*g_rpc_node, tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true); if (TransactionError::OK != err) { throw JSONRPCTransactionError(err, err_string); } @@ -1602,7 +1604,7 @@ UniValue joinpsbts(const JSONRPCRequest& request) for (auto& psbt : psbtxs) { for (unsigned int i = 0; i < psbt.tx->vin.size(); ++i) { if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.tx->vin[i].prevout.hash.ToString().c_str(), psbt.tx->vin[i].prevout.n)); + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.tx->vin[i].prevout.hash.ToString(), psbt.tx->vin[i].prevout.n)); } } for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) { diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index adda90c104..653b287e97 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -13,8 +13,6 @@ #include <tuple> -InitInterfaces* g_rpc_interfaces = nullptr; - void RPCTypeCheck(const UniValue& params, const std::list<UniValueType>& typesExpected, bool fAllowNull) diff --git a/src/rpc/util.h b/src/rpc/util.h index ec36956c95..221638aa9e 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -25,12 +25,6 @@ class FillableSigningProvider; class CPubKey; class CScript; -struct InitInterfaces; - -//! Pointers to interfaces that need to be accessible from RPC methods. Due to -//! limitations of the RPC framework, there's currently no direct way to pass in -//! state to RPC method implementations. -extern InitInterfaces* g_rpc_interfaces; /** Wrapper for UniValue::VType, which includes typeAny: * Used to denote don't care type. */ diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 536807e1d8..4b27ef0ca9 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -645,7 +645,7 @@ NODISCARD bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& } uint32_t p; if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p)) { - error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end()).c_str()); + error = strprintf("Key path value '%s' is not a valid uint32", std::string(elem.begin(), elem.end())); return false; } else if (p > 0x7FFFFFFFUL) { error = strprintf("Key path value %u is out of range", p); @@ -783,7 +783,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(Span<const char>& sp, ParseScriptCon uint32_t thres; std::vector<std::unique_ptr<PubkeyProvider>> providers; if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) { - error = strprintf("Multi threshold '%s' is not valid", std::string(threshold.begin(), threshold.end()).c_str()); + error = strprintf("Multi threshold '%s' is not valid", std::string(threshold.begin(), threshold.end())); return nullptr; } size_t script_size = 0; diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 4eec2311d4..c40fecac5c 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -63,8 +63,6 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide class FillableSigningProvider : public SigningProvider { protected: - mutable CCriticalSection cs_KeyStore; - using KeyMap = std::map<CKeyID, CKey>; using ScriptMap = std::map<CScriptID, CScript>; @@ -74,6 +72,8 @@ protected: void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); public: + mutable CCriticalSection cs_KeyStore; + virtual bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey); virtual bool AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } virtual bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; diff --git a/src/sync.cpp b/src/sync.cpp index 653800ae4e..257093fad1 100644 --- a/src/sync.cpp +++ b/src/sync.cpp @@ -173,7 +173,7 @@ void AssertLockHeldInternal(const char* pszName, const char* pszFile, int nLine, for (const std::pair<void*, CLockLocation>& i : g_lockstack) if (i.first == cs) return; - tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); + tfm::format(std::cerr, "Assertion failed: lock %s not held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld()); abort(); } @@ -181,7 +181,7 @@ void AssertLockNotHeldInternal(const char* pszName, const char* pszFile, int nLi { for (const std::pair<void*, CLockLocation>& i : g_lockstack) { if (i.first == cs) { - tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld().c_str()); + tfm::format(std::cerr, "Assertion failed: lock %s held in %s:%i; locks held:\n%s", pszName, pszFile, nLine, LocksHeld()); abort(); } } diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 5ae0812243..faff1931cd 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -7,8 +7,8 @@ #include <rpc/util.h> #include <core_io.h> -#include <init.h> #include <interfaces/chain.h> +#include <node/context.h> #include <test/setup_common.h> #include <util/time.h> @@ -112,14 +112,14 @@ BOOST_AUTO_TEST_CASE(rpc_rawsign) std::string notsigned = r.get_str(); std::string privkey1 = "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\""; std::string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\""; - InitInterfaces interfaces; - interfaces.chain = interfaces::MakeChain(); - g_rpc_interfaces = &interfaces; + NodeContext node; + node.chain = interfaces::MakeChain(node); + g_rpc_node = &node; r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" [] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false); r = CallRPC(std::string("signrawtransactionwithkey ")+notsigned+" ["+privkey1+","+privkey2+"] "+prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); - g_rpc_interfaces = nullptr; + g_rpc_node = nullptr; } BOOST_AUTO_TEST_CASE(rpc_createraw_op_return) diff --git a/src/test/setup_common.cpp b/src/test/setup_common.cpp index bbdf1ef830..89a19e172d 100644 --- a/src/test/setup_common.cpp +++ b/src/test/setup_common.cpp @@ -15,6 +15,7 @@ #include <net.h> #include <noui.h> #include <pow.h> +#include <rpc/blockchain.h> #include <rpc/register.h> #include <rpc/server.h> #include <script/sigcache.h> @@ -76,6 +77,7 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha const CChainParams& chainparams = Params(); // Ideally we'd move all the RPC tests to the functional testing framework // instead of unit tests, but for now we need these here. + g_rpc_node = &m_node; RegisterAllCoreRPCCommands(tableRPC); // We have to run a scheduler thread to prevent ActivateBestChain @@ -104,8 +106,8 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha for (int i = 0; i < nScriptCheckThreads - 1; i++) threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); - g_banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); - g_connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. + m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); + m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. } TestingSetup::~TestingSetup() @@ -114,8 +116,9 @@ TestingSetup::~TestingSetup() threadGroup.join_all(); GetMainSignals().FlushBackgroundCallbacks(); GetMainSignals().UnregisterBackgroundSignalScheduler(); - g_connman.reset(); - g_banman.reset(); + g_rpc_node = nullptr; + m_node.connman.reset(); + m_node.banman.reset(); UnloadBlockIndex(); g_chainstate.reset(); pblocktree.reset(); diff --git a/src/test/setup_common.h b/src/test/setup_common.h index 6c9494898c..5731b50e31 100644 --- a/src/test/setup_common.h +++ b/src/test/setup_common.h @@ -8,6 +8,7 @@ #include <chainparamsbase.h> #include <fs.h> #include <key.h> +#include <node/context.h> #include <pubkey.h> #include <random.h> #include <scheduler.h> @@ -67,6 +68,7 @@ private: * Included are coins database, script check threads setup. */ struct TestingSetup : public BasicTestingSetup { + NodeContext m_node; boost::thread_group threadGroup; CScheduler scheduler; diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 34192c6b6a..10e5949d54 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -706,7 +706,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) BOOST_CHECK_EQUAL(nDustThreshold, 546); // dust: t.vout[0].nValue = nDustThreshold - 1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "dust"); // not dust: t.vout[0].nValue = nDustThreshold; BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); @@ -716,14 +718,18 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) dustRelayFee = CFeeRate(3702); // dust: t.vout[0].nValue = 673 - 1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "dust"); // not dust: t.vout[0].nValue = 673; BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); t.vout[0].scriptPubKey = CScript() << OP_1; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // MAX_OP_RETURN_RELAY-byte TX_NULL_DATA (standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); @@ -733,7 +739,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // MAX_OP_RETURN_RELAY+1-byte TX_NULL_DATA (non-standard) t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3804678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3800"); BOOST_CHECK_EQUAL(MAX_OP_RETURN_RELAY + 1, t.vout[0].scriptPubKey.size()); + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // Data payload can be encoded in any way... t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex(""); @@ -748,7 +756,9 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) // ...so long as it only contains PUSHDATA's t.vout[0].scriptPubKey = CScript() << OP_RETURN << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptpubkey"); // TX_NULL_DATA w/o PUSHDATA t.vout.resize(1); @@ -759,15 +769,21 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) t.vout.resize(2); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38"); t.vout[1].scriptPubKey = CScript() << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); t.vout[0].scriptPubKey = CScript() << OP_RETURN; t.vout[1].scriptPubKey = CScript() << OP_RETURN; + reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "multi-op-return"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util.cpp b/src/test/util.cpp index b7bb6deeaa..ed031270f2 100644 --- a/src/test/util.cpp +++ b/src/test/util.cpp @@ -32,13 +32,15 @@ std::string getnewaddress(CWallet& w) void importaddress(CWallet& wallet, const std::string& address) { + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); LOCK(wallet.cs_wallet); + AssertLockHeld(spk_man->cs_wallet); const auto dest = DecodeDestination(address); assert(IsValidDestination(dest)); const auto script = GetScriptForDestination(dest); wallet.MarkDirty(); - assert(!wallet.HaveWatchOnly(script)); - if (!wallet.AddWatchOnly(script, 0 /* nCreateTime */)) assert(false); + assert(!spk_man->HaveWatchOnly(script)); + if (!spk_man->AddWatchOnly(script, 0 /* nCreateTime */)) assert(false); wallet.SetAddressBook(dest, /* label */ "", "receive"); } #endif // ENABLE_WALLET diff --git a/src/util/strencodings.cpp b/src/util/strencodings.cpp index 1e7d24c71c..46042f5634 100644 --- a/src/util/strencodings.cpp +++ b/src/util/strencodings.cpp @@ -138,7 +138,7 @@ std::string EncodeBase64(const unsigned char* pch, size_t len) std::string EncodeBase64(const std::string& str) { - return EncodeBase64((const unsigned char*)str.c_str(), str.size()); + return EncodeBase64((const unsigned char*)str.data(), str.size()); } std::vector<unsigned char> DecodeBase64(const char* p, bool* pf_invalid) @@ -207,7 +207,7 @@ std::string EncodeBase32(const unsigned char* pch, size_t len) std::string EncodeBase32(const std::string& str) { - return EncodeBase32((const unsigned char*)str.c_str(), str.size()); + return EncodeBase32((const unsigned char*)str.data(), str.size()); } std::vector<unsigned char> DecodeBase32(const char* p, bool* pf_invalid) diff --git a/src/util/system.cpp b/src/util/system.cpp index 526bf559c3..7da408eda5 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -305,7 +305,7 @@ NODISCARD static bool InterpretOption(std::string key, std::string val, unsigned LogPrintf("Warning: parsed potentially confusing double-negative %s=%s\n", key, val); val = "1"; } else { - error = strprintf("Negating of %s is meaningless and therefore forbidden", key.c_str()); + error = strprintf("Negating of %s is meaningless and therefore forbidden", key); return false; } } @@ -414,7 +414,7 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin return false; } } else { - error = strprintf("Invalid parameter %s", key.c_str()); + error = strprintf("Invalid parameter %s", key); return false; } } @@ -688,7 +688,7 @@ void PrintExceptionContinue(const std::exception* pex, const char* pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); - tfm::format(std::cerr, "\n\n************************\n%s\n", message.c_str()); + tfm::format(std::cerr, "\n\n************************\n%s\n", message); } fs::path GetDefaultDataDir() @@ -870,7 +870,7 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file if (ignore_invalid_keys) { LogPrintf("Ignoring unknown configuration value %s\n", option.first); } else { - error = strprintf("Invalid configuration value %s", option.first.c_str()); + error = strprintf("Invalid configuration value %s", option.first); return false; } } @@ -925,7 +925,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) if (!ReadConfigStream(include_config, to_include, error, ignore_invalid_keys)) { return false; } - LogPrintf("Included configuration file %s\n", to_include.c_str()); + LogPrintf("Included configuration file %s\n", to_include); } else { error = "Failed to include configuration file " + to_include; return false; @@ -945,7 +945,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) } } for (const std::string& to_include : includeconf) { - tfm::format(std::cerr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include.c_str()); + tfm::format(std::cerr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include); } } } @@ -953,7 +953,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) // If datadir is changed in .conf file: ClearDatadirCache(); if (!CheckDataDirOption()) { - error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); + error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "")); return false; } return true; diff --git a/src/util/translation.h b/src/util/translation.h index 0e6eb5a094..fc45da440a 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -6,7 +6,7 @@ #define BITCOIN_UTIL_TRANSLATION_H #include <tinyformat.h> - +#include <functional> /** * Bilingual messages: 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/init.cpp b/src/wallet/init.cpp index 3657a157b6..a8b3df1f2e 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -6,6 +6,7 @@ #include <init.h> #include <interfaces/chain.h> #include <net.h> +#include <node/context.h> #include <outputtype.h> #include <util/moneystr.h> #include <util/system.h> @@ -25,8 +26,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(); @@ -125,12 +126,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..0bc6c90354 100644 --- a/src/wallet/ismine.h +++ b/src/wallet/ismine.h @@ -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..aa13cacca4 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -39,12 +39,12 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps return TransactionError::SIGHASH_MISMATCH; } - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !sign, !bip32derivs), psbtx, i, sighash_type); + complete &= SignPSBTInput(HidingSigningProvider(pwallet->GetSigningProvider(), !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); + UpdatePSBTOutput(HidingSigningProvider(pwallet->GetSigningProvider(), true, !bip32derivs), psbtx, i); } return TransactionError::OK; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9af567fe0d..0eef0502de 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -54,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()) { @@ -125,6 +125,11 @@ UniValue importprivkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + WalletRescanReserver reserver(pwallet); bool fRescan = true; { @@ -251,6 +256,10 @@ UniValue importaddress(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } std::string strLabel; if (!request.params[1].isNull()) @@ -453,6 +462,10 @@ UniValue importpubkey(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } std::string strLabel; if (!request.params[1].isNull()) @@ -536,6 +549,11 @@ UniValue importwallet(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + if (pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), @@ -693,6 +711,11 @@ UniValue dumpprivkey(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -703,12 +726,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); @@ -742,8 +765,14 @@ UniValue dumpwallet(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); + AssertLockHeld(spk_man->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -765,10 +794,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; @@ -787,11 +816,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()); @@ -804,20 +833,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"; @@ -826,11 +855,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); } @@ -1206,7 +1235,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()) + "\")"); } } @@ -1326,6 +1355,11 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + const UniValue& requests = mainRequest.params[0]; //Default options diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e571501221..bfa4cf2bbe 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5,9 +5,9 @@ #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()); @@ -303,7 +303,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 +550,11 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } + const SigningProvider* provider = pwallet->GetSigningProvider(); + 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 +612,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"); } @@ -694,7 +696,7 @@ 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 (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) { if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) nAmount += txout.nValue; } @@ -964,6 +966,11 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -980,7 +987,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 +1000,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); @@ -1064,7 +1071,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 +1295,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); @@ -1319,7 +1326,7 @@ 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); @@ -2379,7 +2386,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)); @@ -2449,7 +2457,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 +2473,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 +2587,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 +2713,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); @@ -2920,10 +2933,11 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("label", i->second.name); } + const SigningProvider* provider = pwallet->GetSigningProvider(); if (scriptPubKey.IsPayToScriptHash()) { const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); CScript redeemScript; - if (pwallet->GetCScript(hash, 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; @@ -2935,7 +2949,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CScriptID id; CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); CScript witnessScript; - if (pwallet->GetCScript(id, witnessScript)) { + if (provider->GetCScript(id, witnessScript)) { entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } @@ -2945,7 +2959,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CScriptID id; CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); CScript witnessScript; - if (pwallet->GetCScript(id, witnessScript)) { + if (provider->GetCScript(id, witnessScript)) { entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } @@ -2957,7 +2971,7 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { - auto descriptor = InferDescriptor(scriptPubKey, *pwallet); + auto descriptor = InferDescriptor(scriptPubKey, *pwallet->GetLegacyScriptPubKeyMan()); entry.pushKV("desc", descriptor->ToString()); } if (avoid_reuse) entry.pushKV("reused", reused); @@ -3267,7 +3281,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[1], nullptr, coins); - return SignTransaction(mtx, pwallet, coins, request.params[2]); + return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]); } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3539,7 +3553,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 +3589,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 +3598,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 +3610,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 +3620,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 +3633,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 +3646,12 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); + const SigningProvider* provider = nullptr; + if (pwallet) { + provider = pwallet->GetSigningProvider(); + } ret.pushKVs(detail); - ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest)); + ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider), dest)); return ret; } @@ -3722,13 +3740,14 @@ UniValue getaddressinfo(const JSONRPCRequest& request) CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); + const SigningProvider* provider = pwallet->GetSigningProvider(); - isminetype mine = IsMine(*pwallet, dest); + isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = IsSolvable(*pwallet, scriptPubKey); + bool solvable = 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); @@ -3738,7 +3757,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); const CKeyMetadata* meta = nullptr; - CKeyID key_id = GetKeyForDestination(*pwallet, dest); + CKeyID key_id = GetKeyForDestination(*provider, dest); if (!key_id.IsNull()) { auto it = pwallet->mapKeyMetadata.find(key_id); if (it != pwallet->mapKeyMetadata.end()) { @@ -3916,6 +3935,11 @@ UniValue sethdseed(const JSONRPCRequest& request) }, }.Check(request); + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } @@ -3941,22 +3965,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 +4255,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..31d3f7a5f9 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -21,6 +21,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); /** diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp new file mode 100644 index 0000000000..c13fddfaf3 --- /dev/null +++ b/src/wallet/scriptpubkeyman.cpp @@ -0,0 +1,1274 @@ +// 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> +#include <wallet/wallet.h> + +bool LegacyScriptPubKeyMan::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; + } + LearnRelatedScripts(new_key, type); + dest = GetDestinationForKey(new_key, type); + + m_wallet.SetAddressBook(dest, label, "receive"); + 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 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; + } + 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 LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +{ + LOCK(cs_KeyStore); + if (!mapCryptedKeys.empty() || IsCrypted()) + return false; + + 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; + } + mapKeys.clear(); + return true; +} + +void LegacyScriptPubKeyMan::UpgradeKeyMetadata() +{ + AssertLockHeld(cs_wallet); + 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); + } + } + } + batch.reset(); //write before setting the flag + m_storage.SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); +} + +bool LegacyScriptPubKeyMan::IsHDEnabled() const +{ + return !hdChain.seed_id.IsNull(); +} + +bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) +{ + LOCK(cs_wallet); + // 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; +} + +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_wallet); + + 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() +{ + AssertLockHeld(cs_wallet); + return setExternalKeyPool.size() + set_pre_split_keypool.size(); +} + +/** + * 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_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 LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + WalletBatch batch(m_storage.GetDatabase()); + return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey); +} + +bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) +{ + // 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 (!IsCrypted()) { + return batch.WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); + } + m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + 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) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + mapKeyMetadata[keyID] = meta; +} + +void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) +{ + AssertLockHeld(cs_wallet); + UpdateTimeFirstKey(meta.nCreateTime); + m_script_metadata[script_id] = meta; +} + +bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::AddKeyPubKey(key, pubkey); + } + + if (m_storage.IsLocked()) { + return false; + } + + std::vector<unsigned char> vchCryptedSecret; + CKeyingMaterial vchSecret(key.begin(), key.end()); + if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + return false; + } + + if (!AddCryptedKey(pubkey, vchCryptedSecret)) { + return false; + } + 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); + if (!SetCrypted()) { + return false; + } + + 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_wallet); + 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) +{ + 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). + } + + 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.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + 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_wallet); + 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 (!IsCrypted()) { + return FillableSigningProvider::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + +bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + 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(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + } + return false; +} + +bool LegacyScriptPubKeyMan::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 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 (!IsCrypted()) { + 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_wallet); + 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) +{ + 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); + } + 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_wallet); + 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_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 LegacyScriptPubKeyMan::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 = m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; + newHdChain.seed_id = seed.GetID(); + SetHDChain(newHdChain, false); + NotifyCanGetAddressesChanged(); + m_wallet.UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); +} + +/** + * 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_wallet); + 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 (!TopUpKeyPool()) { + return false; + } + WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n"); + } + return true; +} + +bool LegacyScriptPubKeyMan::TopUpKeyPool(unsigned int kpSize) +{ + if (!CanGenerateKeys()) { + return false; + } + { + LOCK(cs_wallet); + + 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_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; +} + +void LegacyScriptPubKeyMan::KeepKey(int64_t nIndex) +{ + // Remove from key pool + WalletBatch batch(m_storage.GetDatabase()); + batch.ErasePool(nIndex); + WalletLogPrintf("keypool keep %d\n", nIndex); +} + +void LegacyScriptPubKeyMan::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(); + } + WalletLogPrintf("keypool return %d\n", nIndex); +} + +bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) +{ + if (!CanGetAddresses(internal)) { + return false; + } + + CKeyPool keypool; + { + LOCK(cs_wallet); + 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; + } + KeepKey(nIndex); + result = keypool.vchPubKey; + } + return true; +} + +bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal) +{ + nIndex = -1; + keypool.vchPubKey = CPubKey(); + { + LOCK(cs_wallet); + + TopUpKeyPool(); + + 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"); + } + + 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_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(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.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; +} + +bool LegacyScriptPubKeyMan::AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info) +{ + 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 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::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, 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; + } + } + CTxDestination dest; + ExtractDestination(script, dest); + if (apply_label && IsValidDestination(dest)) { + m_wallet.SetAddressBookWithDB(batch, dest, label, "receive"); + } + } + return true; +} + +std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const +{ + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return FillableSigningProvider::GetKeys(); + } + std::set<CKeyID> set_address; + for (const auto& mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +// Temporary CWallet accessors and aliases. +LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet& wallet) + : ScriptPubKeyMan(wallet), + m_wallet(wallet), + cs_wallet(wallet.cs_wallet), + vMasterKey(wallet.vMasterKey), + fUseCrypto(wallet.fUseCrypto), + fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {} + +bool LegacyScriptPubKeyMan::SetCrypted() { return m_wallet.SetCrypted(); } +bool LegacyScriptPubKeyMan::IsCrypted() const { return m_wallet.IsCrypted(); } +void LegacyScriptPubKeyMan::NotifyWatchonlyChanged(bool fHaveWatchOnly) const { return m_wallet.NotifyWatchonlyChanged(fHaveWatchOnly); } +void LegacyScriptPubKeyMan::NotifyCanGetAddressesChanged() const { return m_wallet.NotifyCanGetAddressesChanged(); } +template<typename... Params> void LegacyScriptPubKeyMan::WalletLogPrintf(const std::string& fmt, const Params&... parameters) const { return m_wallet.WalletLogPrintf(fmt, parameters...); } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h new file mode 100644 index 0000000000..55184098b7 --- /dev/null +++ b/src/wallet/scriptpubkeyman.h @@ -0,0 +1,355 @@ +// 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. + +#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. +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 SetWalletFlag(uint64_t) = 0; + virtual void UnsetWalletFlagWithDB(WalletBatch&, uint64_t) = 0; + virtual bool CanSupportFeature(enum WalletFeature) const = 0; + virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0; + virtual bool IsLocked() const = 0; +}; + +//! Default for -keypool +static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; + +/** 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 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) {} +}; + +class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider +{ +private: + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; + using WatchOnlySet = std::set<CScript>; + using WatchKeyMap = std::map<CKeyID, CPubKey>; + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + 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); + + WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; + + /* 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; + + 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); + + //! Adds a script to the store and saves it to disk + bool AddCScriptWithDB(WalletBatch& batch, const CScript& script); + + public: + //! Fetches a key from the keypool + bool GetKeyFromPool(CPubKey &key, bool internal = false); + 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); + + /** + * 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); + 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 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; + + bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + 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_wallet); + bool 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) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + 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; } + bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); + + isminetype IsMine(const CScript& script) const; + + /* 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); + + /** + * 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); + + /** Implement lookup of key origin information through wallet key metadata. */ + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + + // Temporary CWallet accessors and aliases. + friend class CWallet; + friend class ReserveDestination; + LegacyScriptPubKeyMan(CWallet& wallet); + bool SetCrypted(); + bool IsCrypted() const; + void NotifyWatchonlyChanged(bool fHaveWatchOnly) const; + void NotifyCanGetAddressesChanged() const; + template<typename... Params> void WalletLogPrintf(const std::string& fmt, const Params&... parameters) const; + CWallet& m_wallet; + CCriticalSection& cs_wallet; + CKeyingMaterial& vMasterKey GUARDED_BY(cs_KeyStore); + std::atomic<bool>& fUseCrypto; + bool& fDecryptionThoroughlyChecked; +}; + +#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 9e7f0ed773..397e6ea9d3 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -2,6 +2,7 @@ // 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> @@ -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; diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index e2b7075085..eb4e72c88b 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -6,6 +6,7 @@ #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #include <interfaces/chain.h> +#include <node/context.h> #include <test/setup_common.h> @@ -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/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 062fef7748..24636fd599 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -3,6 +3,7 @@ // 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> @@ -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; @@ -38,12 +40,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -54,12 +56,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -70,12 +72,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -86,12 +88,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -104,17 +106,17 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -127,11 +129,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -144,11 +146,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -160,10 +162,10 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -176,11 +178,11 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -188,13 +190,13 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + 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); } @@ -202,17 +204,17 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); + 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); } @@ -224,25 +226,25 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -250,19 +252,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - BOOST_CHECK(keystore.AddKey(keys[1])); + 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); } @@ -270,24 +272,24 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); - BOOST_CHECK(keystore.AddKey(keys[1])); + 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); } @@ -295,24 +297,24 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(uncompressedKey)); - BOOST_CHECK(keystore.AddKey(keys[1])); + 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); } @@ -326,19 +328,19 @@ BOOST_AUTO_TEST_CASE(ismine_standard) 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); } @@ -346,12 +348,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + 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); } @@ -359,12 +361,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + 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); } @@ -372,12 +374,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + 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); } @@ -385,12 +387,12 @@ BOOST_AUTO_TEST_CASE(ismine_standard) { CWallet keystore(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(keystore.cs_wallet); - BOOST_CHECK(keystore.AddKey(keys[0])); + 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..27a64ff12f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -16,6 +16,7 @@ BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) BOOST_AUTO_TEST_CASE(psbt_updater_test) { + auto spk_man = m_wallet.GetLegacyScriptPubKeyMan(); LOCK(m_wallet.cs_wallet); // Create prevtxs and add to wallet @@ -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; diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index c1dbecdf8c..def6f1934e 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -9,6 +9,7 @@ #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..72e1b4e83b 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -9,6 +9,7 @@ #include <vector> #include <interfaces/chain.h> +#include <node/context.h> #include <policy/policy.h> #include <rpc/server.h> #include <test/setup_common.h> @@ -27,8 +28,10 @@ BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet& wallet, const CKey& key) { + auto spk_man = wallet.GetLegacyScriptPubKeyMan(); LOCK(wallet.cs_wallet); - wallet.AddKeyPubKey(key, key.GetPubKey()); + AssertLockHeld(spk_man->cs_wallet); + spk_man->AddKeyPubKey(key, key.GetPubKey()); } BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) @@ -39,7 +42,8 @@ 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); @@ -118,7 +122,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); @@ -185,7 +190,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 +200,11 @@ 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()); + auto spk_man = wallet->GetLegacyScriptPubKeyMan(); LOCK(wallet->cs_wallet); - wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; - wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + AssertLockHeld(spk_man->cs_wallet); + spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; + spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); JSONRPCRequest request; request.params.setArray(); @@ -239,14 +247,17 @@ 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.GetLegacyScriptPubKeyMan(); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LockAssertion lock(::cs_main); LOCK(wallet.cs_wallet); + AssertLockHeld(spk_man->cs_wallet); wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); @@ -254,10 +265,10 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); - // Invalidate the cached value, add the key, and make sure a new immature + // Invalidate the cached vanue, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); - wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); } @@ -337,37 +348,38 @@ 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_wallet); // 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)); + AssertLockHeld(spk_man->cs_wallet); + 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 +394,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.GetLegacyScriptPubKeyMan(); - 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 @@ -466,7 +479,8 @@ public: 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,7 +552,8 @@ 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->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 159d4f78c6..4b1adfb38f 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -210,9 +210,9 @@ 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(); + CPubKey master_pub_key = wallet->m_spk_man->GenerateNewSeed(); + wallet->m_spk_man->SetHDSeed(master_pub_key); + wallet->m_spk_man->NewKeyPool(); // Relock the wallet wallet->Lock(); @@ -224,8 +224,6 @@ 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")); /** @defgroup mapWallet @@ -238,17 +236,7 @@ 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; -} +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider); const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { @@ -259,354 +247,12 @@ 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; + AssertLockHeld(m_spk_man->cs_wallet); + if (m_spk_man) { + m_spk_man->UpgradeKeyMetadata(); } - - 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). - } - - 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()); } bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) @@ -887,14 +533,15 @@ 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); + if (auto spk_man = m_spk_man.get()) { + if (!spk_man->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); + } } // Encryption was introduced in version 0.4.0 @@ -915,11 +562,11 @@ 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 (m_spk_man->IsHDEnabled()) { + m_spk_man->SetHDSeed(m_spk_man->GenerateNewSeed()); } - NewKeyPool(); + m_spk_man->NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might keep @@ -1051,7 +698,7 @@ void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { - if (::IsMine(*this, dst)) { + if (IsMine(dst)) { LOCK(cs_wallet); if (used && !GetDestData(dst, "used", nullptr)) { AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null) @@ -1065,7 +712,7 @@ void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool bool CWallet::IsUsedDestination(const CTxDestination& dst) const { LOCK(cs_wallet); - return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr); + return IsMine(dst) && GetDestData(dst, "used", nullptr); } bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const @@ -1225,13 +872,13 @@ 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()) { + for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *m_spk_man)) { + std::map<CKeyID, int64_t>::const_iterator mi = m_spk_man->m_pool_key_to_index.find(keyid); + if (mi != m_spk_man->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()) { + if (!m_spk_man->TopUpKeyPool()) { WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); } } @@ -1487,7 +1134,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; + if (auto spk_man = m_spk_man.get()) { + result = spk_man->IsMine(script); + } + return result; } CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const @@ -1511,7 +1172,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 +1262,24 @@ 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; + if (auto spk_man = m_spk_man.get()) { + result &= spk_man->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(); + { + auto spk_man = m_spk_man.get(); + if (spk_man && spk_man->CanGetAddresses(internal)) { + return true; + } } - return keypool_has_keys; + return false; } void CWallet::SetWalletFlag(uint64_t flags) @@ -1745,7 +1338,9 @@ 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)) { + const SigningProvider* provider = GetSigningProvider(); + + if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } UpdateInput(tx_in, sigdata); @@ -1770,97 +1365,43 @@ 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; + AssertLockHeld(spk_man->cs_wallet); + 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; + AssertLockHeld(spk_man->cs_wallet); + 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; + AssertLockHeld(spk_man->cs_wallet); + 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; - } - } - CTxDestination dest; - ExtractDestination(script, dest); - if (apply_label && IsValidDestination(dest)) { - SetAddressBookWithDB(batch, dest, label, "receive"); - } + auto spk_man = GetLegacyScriptPubKeyMan(); + if (!spk_man) { + return false; + } + AssertLockHeld(spk_man->cs_wallet); + if (!spk_man->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, apply_label, timestamp)) { + return false; } return true; } @@ -2541,7 +2082,9 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); + const SigningProvider* provider = GetSigningProvider(); + + 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))); @@ -2775,7 +2318,13 @@ 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)) { + + const SigningProvider* provider = GetSigningProvider(); + if (!provider) { + return false; + } + + if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { return false; } UpdateInput(input, sigdata); @@ -3233,7 +2782,12 @@ 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)) + const SigningProvider* provider = GetSigningProvider(); + if (!provider) { + return false; + } + + if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; return false; @@ -3341,7 +2895,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->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. @@ -3378,7 +2932,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->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. @@ -3403,7 +2957,7 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); - m_pool_key_to_index.clear(); + m_spk_man->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. @@ -3427,7 +2981,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,258 +3008,50 @@ 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; + if (auto spk_man = m_spk_man.get()) { + AssertLockHeld(spk_man->cs_wallet); + 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) { - 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; - } - - 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 CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch) -{ - 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(); - } - 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; + bool res = true; + if (auto spk_man = m_spk_man.get()) { + res &= spk_man->TopUpKeyPool(kpSize); } - 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 = m_spk_man.get(); + if (spk_man) { + result = spk_man->GetNewDestination(type, label, dest, error); } - 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) { error.clear(); - TopUpKeyPool(); + m_spk_man->TopUpKeyPool(); ReserveDestination reservedest(this); if (!reservedest.GetReservedDestination(type, dest, true)) { @@ -3717,35 +3063,12 @@ 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); - - 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); - } + int64_t oldestKey = std::numeric_limits<int64_t>::max(); + if (auto spk_man = m_spk_man.get()) { + oldestKey = spk_man->GetOldestKeyPoolTime(); } - return oldestKey; } @@ -3898,6 +3221,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) { + m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (!m_spk_man) { + return false; + } + if (!pwallet->CanGetAddresses(internal)) { return false; } @@ -3905,14 +3233,14 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin if (nIndex == -1) { CKeyPool keypool; - if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { + if (!m_spk_man->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { return false; } vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); - pwallet->LearnRelatedScripts(vchPubKey, type); + m_spk_man->LearnRelatedScripts(vchPubKey, type); address = GetDestinationForKey(vchPubKey, type); dest = address; return true; @@ -3921,7 +3249,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin void ReserveDestination::KeepDestination() { if (nIndex != -1) - pwallet->KeepKey(nIndex); + m_spk_man->KeepKey(nIndex); nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); @@ -3930,37 +3258,13 @@ void ReserveDestination::KeepDestination() void ReserveDestination::ReturnDestination() { if (nIndex != -1) { - pwallet->ReturnKey(nIndex, fInternal, vchPubKey); + m_spk_man->ReturnKey(nIndex, fInternal, vchPubKey); } 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 +3307,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); + AssertLockHeld(spk_man->cs_wallet); + // 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 +3322,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 +3339,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) @@ -4156,24 +3464,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: @@ -4316,13 +3606,13 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, bool hd_upgrade = false; bool split_upgrade = false; - if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { + if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->m_spk_man->IsHDEnabled()) { walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); walletInstance->SetMinVersion(FEATURE_HD); // generate a new master key - CPubKey masterPubKey = walletInstance->GenerateNewSeed(); - walletInstance->SetHDSeed(masterPubKey); + CPubKey masterPubKey = walletInstance->m_spk_man->GenerateNewSeed(); + walletInstance->m_spk_man->SetHDSeed(masterPubKey); hd_upgrade = true; } // Upgrade to HD chain split if necessary @@ -4337,7 +3627,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } // Regenerate the keypool if upgraded to HD if (hd_upgrade) { - if (!walletInstance->TopUpKeyPool()) { + if (!walletInstance->m_spk_man->TopUpKeyPool()) { error = _("Unable to generate keys").translated; return nullptr; } @@ -4352,12 +3642,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, 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); + CPubKey seed = walletInstance->m_spk_man->GenerateNewSeed(); + walletInstance->m_spk_man->SetHDSeed(seed); } // Top up the keypool - if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { + if (walletInstance->m_spk_man->CanGenerateKeys() && !walletInstance->m_spk_man->TopUpKeyPool()) { error = _("Unable to generate initial keys").translated; return nullptr; } @@ -4650,23 +3940,6 @@ bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const 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); -} - std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const { std::vector<OutputGroup> groups; std::map<CTxDestination, OutputGroup> gmap; @@ -4697,35 +3970,6 @@ 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) -{ - 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); @@ -4760,168 +4004,17 @@ bool CWallet::Lock() return true; } -bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +ScriptPubKeyMan* CWallet::GetScriptPubKeyMan() const { - { - 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; - } - 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; + return m_spk_man.get(); } -bool CWallet::HaveKey(const CKeyID &address) const +const SigningProvider* CWallet::GetSigningProvider() const { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::HaveKey(address); - } - return mapCryptedKeys.count(address) > 0; + return m_spk_man.get(); } -bool CWallet::GetKey(const CKeyID &address, CKey& keyOut) const +LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - 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(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); - } - return false; -} - -bool CWallet::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 CWallet::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - 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); -} - -std::set<CKeyID> CWallet::GetKeys() const -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::GetKeys(); - } - std::set<CKeyID> set_address; - for (const auto& mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; -} - -bool CWallet::EncryptKeys(CKeyingMaterial& vMasterKeyIn) -{ - LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) - return false; - - 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; - } - mapKeys.clear(); - return true; -} - -bool CWallet::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) -{ - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return FillableSigningProvider::AddKeyPubKey(key, pubkey); - } - - if (IsLocked()) { - return false; - } - - std::vector<unsigned char> vchCryptedSecret; - CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { - return false; - } - - if (!AddCryptedKey(pubkey, vchCryptedSecret)) { - return false; - } - return true; -} - - -bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) -{ - LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } - - mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); - ImplicitlyLearnRelatedKeyScripts(vchPubKey); - return true; + return m_spk_man.get(); } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 85c277ff50..f3b791441c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -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> @@ -57,8 +57,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 @@ -99,58 +97,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 +121,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. @@ -282,6 +141,7 @@ class ReserveDestination protected: //! The wallet to reserve from CWallet* pwallet; + LegacyScriptPubKeyMan* m_spk_man{nullptr}; //! The index of the address's key in the keypool int64_t nIndex{-1}; //! The public key for the address @@ -722,10 +582,9 @@ 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); @@ -737,22 +596,8 @@ private: //! 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>; - 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 +606,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}; @@ -812,52 +655,12 @@ private: * 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); - /* 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); + void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag) override; /** Interface for accessing chain state. */ interfaces::Chain* m_chain; @@ -878,9 +681,6 @@ private: */ uint256 m_last_block_processed GUARDED_BY(cs_wallet); - //! Fetches a key from the keypool - bool GetKeyFromPool(CPubKey &key, bool internal = false); - public: /* * Main wallet lock. @@ -895,6 +695,7 @@ public: { return *database; } + WalletDatabase& GetDatabase() override { return *database; } /** * Select a set of coins such that nValueRet >= nTargetValue and at least @@ -910,15 +711,6 @@ 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; @@ -942,7 +734,7 @@ public: } bool IsCrypted() const { return fUseCrypto; } - bool IsLocked() const; + bool IsLocked() const override; bool Lock(); /** Interface to assert chain access and if successful lock it */ @@ -972,7 +764,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. @@ -1022,34 +814,10 @@ 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); @@ -1062,18 +830,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; @@ -1189,33 +945,10 @@ 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); @@ -1225,6 +958,8 @@ public: 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 @@ -1261,7 +996,7 @@ public: } //! 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); @@ -1340,31 +1075,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,35 +1089,21 @@ 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); + void SetWalletFlag(uint64_t flags) override; /** Unsets a single wallet flag */ 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 +1114,28 @@ 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; + ScriptPubKeyMan* GetScriptPubKeyMan() const; + const SigningProvider* GetSigningProvider() const; + LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; + + // Temporary LegacyScriptPubKeyMan accessors and aliases. + friend class LegacyScriptPubKeyMan; + std::unique_ptr<LegacyScriptPubKeyMan> m_spk_man = MakeUnique<LegacyScriptPubKeyMan>(*this); + CCriticalSection& cs_KeyStore = m_spk_man->cs_KeyStore; + LegacyScriptPubKeyMan::KeyMap& mapKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapKeys; + LegacyScriptPubKeyMan::ScriptMap& mapScripts GUARDED_BY(cs_KeyStore) = m_spk_man->mapScripts; + LegacyScriptPubKeyMan::CryptedKeyMap& mapCryptedKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapCryptedKeys; + LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly; + LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; + WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; + std::set<int64_t>& setInternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setInternalKeyPool; + std::set<int64_t>& setExternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setExternalKeyPool; + int64_t& nTimeFirstKey GUARDED_BY(cs_wallet) = m_spk_man->nTimeFirstKey; + std::map<CKeyID, CKeyMetadata>& mapKeyMetadata GUARDED_BY(cs_wallet) = m_spk_man->mapKeyMetadata; + std::map<CScriptID, CKeyMetadata>& m_script_metadata GUARDED_BY(cs_wallet) = m_spk_man->m_script_metadata; + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkPreSplitKeys(); } + void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkReserveKeysAsUsed(keypool_id); } + using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; }; /** diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index a9e6763c6d..2ba7cdac36 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -196,7 +196,7 @@ public: static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, - CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) + CWalletScanState &wss, std::string& strType, std::string& strErr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet, pwallet->GetLegacyScriptPubKeyMan()->cs_wallet) { try { // Unserialize @@ -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->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool); } else if (strType == DBKeys::CSCRIPT) { uint160 hash; ssKey >> hash; CScript script; ssValue >> script; - if (!pwallet->LoadCScript(script)) + if (!pwallet->GetLegacyScriptPubKeyMan()->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->GetLegacyScriptPubKeyMan()->SetHDChain(chain, true); } else if (strType == DBKeys::FLAGS) { uint64_t flags; ssValue >> flags; @@ -432,6 +434,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) DBErrors result = DBErrors::LOAD_OK; LOCK(pwallet->cs_wallet); + AssertLockHeld(pwallet->GetLegacyScriptPubKeyMan()->cs_wallet); try { int nMinVersion = 0; if (m_batch.Read(DBKeys::MINVERSION, nMinVersion)) { @@ -512,8 +515,12 @@ 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->GetLegacyScriptPubKeyMan(); + if (spk_man) { + spk_man->UpdateTimeFirstKey(1); + } + } for (const uint256& hash : wss.vWalletUpgrade) WriteTx(pwallet->mapWallet.at(hash)); @@ -706,6 +713,7 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C { // Required in LoadKeyMetadata(): LOCK(dummyWallet->cs_wallet); + AssertLockHeld(dummyWallet->GetLegacyScriptPubKeyMan()->cs_wallet); fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, dummyWss, strType, strErr); } diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 0843194511..dc0cac60bd 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -30,15 +30,16 @@ static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs:: 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->GetLegacyScriptPubKeyMan(); + CPubKey seed = spk_man->GenerateNewSeed(); + spk_man->SetHDSeed(seed); tfm::format(std::cout, "Topping up keypool...\n"); wallet_instance->TopUpKeyPool(); @@ -59,28 +60,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 +95,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 +113,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 +126,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/walletutil.h b/src/wallet/walletutil.h index ba2f913841..044c757e68 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -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(); diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 2e1fdf4f3a..7ccda1c566 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_WALLETINITINTERFACE_H #define BITCOIN_WALLETINITINTERFACE_H -struct InitInterfaces; +struct NodeContext; class WalletInitInterface { public: @@ -15,8 +15,8 @@ public: virtual void AddWalletOptions() const = 0; /** Check wallet parameter interaction */ virtual bool ParameterInteraction() const = 0; - /** Add wallets that should be opened to list of init interfaces. */ - virtual void Construct(InitInterfaces& interfaces) const = 0; + /** Add wallets that should be opened to list of chain clients. */ + virtual void Construct(NodeContext& node) const = 0; virtual ~WalletInitInterface() {} }; |