diff options
Diffstat (limited to 'src/wallet')
33 files changed, 2762 insertions, 1920 deletions
diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index 48a924abfb..9257b272bc 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -36,6 +36,8 @@ public: bool m_avoid_partial_spends; //! Fee estimation mode to control arguments to estimateSmartFee FeeEstimateMode m_fee_mode; + //! Minimum chain depth value for coin availability + int m_min_depth{0}; CCoinControl() { diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index f5ac6e98b2..a255177e36 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -175,14 +175,14 @@ bool CCryptoKeyStore::Lock() return true; } -bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) +bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) { { LOCK(cs_KeyStore); if (!SetCrypted()) return false; - bool keyPass = 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) @@ -204,7 +204,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) 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) + if (keyFail || (!keyPass && !accept_no_keys)) return false; vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 418316c398..8e195ca8fa 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -133,7 +133,7 @@ protected: //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); - bool Unlock(const CKeyingMaterial& vMasterKeyIn); + bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); public: diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 98e2abbd18..6a326bfd97 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -48,7 +48,7 @@ void CheckUniqueFileid(const BerkeleyEnvironment& env, const std::string& filena } CCriticalSection cs_db; -std::map<std::string, BerkeleyEnvironment> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to open db environment. +std::map<std::string, std::weak_ptr<BerkeleyEnvironment>> g_dbenvs GUARDED_BY(cs_db); //!< Map from directory name to db environment. } // namespace bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId& rhs) const @@ -80,19 +80,37 @@ bool IsWalletLoaded(const fs::path& wallet_path) LOCK(cs_db); auto env = g_dbenvs.find(env_directory.string()); if (env == g_dbenvs.end()) return false; - return env->second.IsDatabaseLoaded(database_filename); + auto database = env->second.lock(); + return database && database->IsDatabaseLoaded(database_filename); } -BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) +fs::path WalletDataFilePath(const fs::path& wallet_path) +{ + fs::path env_directory; + std::string database_filename; + SplitWalletPath(wallet_path, env_directory, database_filename); + return env_directory / database_filename; +} + +/** + * @param[in] wallet_path Path to wallet directory. Or (for backwards compatibility only) a path to a berkeley btree data file inside a wallet directory. + * @param[out] database_filename Filename of berkeley btree data file inside the wallet directory. + * @return A shared pointer to the BerkeleyEnvironment object for the wallet directory, never empty because ~BerkeleyEnvironment + * erases the weak pointer from the g_dbenvs map. + * @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the directory path key was not already in the map. + */ +std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename) { fs::path env_directory; SplitWalletPath(wallet_path, env_directory, database_filename); LOCK(cs_db); - // Note: An unused temporary BerkeleyEnvironment object may be created inside the - // emplace function if the key already exists. This is a little inefficient, - // but not a big concern since the map will be changed in the future to hold - // pointers instead of objects, anyway. - return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second; + auto inserted = g_dbenvs.emplace(env_directory.string(), std::weak_ptr<BerkeleyEnvironment>()); + if (inserted.second) { + auto env = std::make_shared<BerkeleyEnvironment>(env_directory.string()); + inserted.first->second = env; + return env; + } + return inserted.first->second.lock(); } // @@ -116,11 +134,18 @@ void BerkeleyEnvironment::Close() } } + FILE* error_file = nullptr; + dbenv->get_errfile(&error_file); + int ret = dbenv->close(0); if (ret != 0) LogPrintf("BerkeleyEnvironment::Close: Error %d closing database environment: %s\n", ret, DbEnv::strerror(ret)); if (!fMockDb) DbEnv((u_int32_t)0).remove(strPath.c_str(), 0); + + if (error_file) fclose(error_file); + + UnlockDirectory(strPath, ".walletlock"); } void BerkeleyEnvironment::Reset() @@ -137,6 +162,8 @@ BerkeleyEnvironment::BerkeleyEnvironment(const fs::path& dir_path) : strPath(dir BerkeleyEnvironment::~BerkeleyEnvironment() { + LOCK(cs_db); + g_dbenvs.erase(strPath); Close(); } @@ -214,10 +241,10 @@ bool BerkeleyEnvironment::Open(bool retry) return true; } -void BerkeleyEnvironment::MakeMock() +//! Construct an in-memory mock Berkeley environment for testing and as a place-holder for g_dbenvs emplace +BerkeleyEnvironment::BerkeleyEnvironment() { - if (fDbEnvInit) - throw std::runtime_error("BerkeleyEnvironment::MakeMock: Already initialized"); + Reset(); boost::this_thread::interruption_point(); @@ -263,10 +290,49 @@ BerkeleyEnvironment::VerifyResult BerkeleyEnvironment::Verify(const std::string& return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL); } +BerkeleyBatch::SafeDbt::SafeDbt() +{ + m_dbt.set_flags(DB_DBT_MALLOC); +} + +BerkeleyBatch::SafeDbt::SafeDbt(void* data, size_t size) + : m_dbt(data, size) +{ +} + +BerkeleyBatch::SafeDbt::~SafeDbt() +{ + if (m_dbt.get_data() != nullptr) { + // Clear memory, e.g. in case it was a private key + memory_cleanse(m_dbt.get_data(), m_dbt.get_size()); + // under DB_DBT_MALLOC, data is malloced by the Dbt, but must be + // freed by the caller. + // https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html + if (m_dbt.get_flags() & DB_DBT_MALLOC) { + free(m_dbt.get_data()); + } + } +} + +const void* BerkeleyBatch::SafeDbt::get_data() const +{ + return m_dbt.get_data(); +} + +u_int32_t BerkeleyBatch::SafeDbt::get_size() const +{ + return m_dbt.get_size(); +} + +BerkeleyBatch::SafeDbt::operator Dbt*() +{ + return &m_dbt; +} + bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename) { std::string filename; - BerkeleyEnvironment* env = GetWalletEnv(file_path, filename); + std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename); // Recovery procedure: // move wallet file to walletfilename.timestamp.bak @@ -335,11 +401,11 @@ bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, boo bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr) { std::string walletFile; - BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile); + std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile); fs::path walletDir = env->Directory(); - LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); - LogPrintf("Using wallet %s\n", walletFile); + LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(nullptr, nullptr, nullptr)); + LogPrintf("Using wallet %s\n", file_path.string()); // Wallet file must be a plain filename without a directory if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) @@ -359,7 +425,7 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc) { std::string walletFile; - BerkeleyEnvironment* env = GetWalletEnv(file_path, walletFile); + std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile); fs::path walletDir = env->Directory(); if (fs::exists(walletDir / walletFile)) @@ -463,7 +529,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo { fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); fFlushOnClose = fFlushOnCloseIn; - env = database.env; + env = database.env.get(); if (database.IsDummy()) { return; } @@ -520,7 +586,7 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const char* pszMode, bo // versions of BDB have an set_lk_exclusive method for this // purpose, but the older version we use does not.) for (const auto& env : g_dbenvs) { - CheckUniqueFileid(env.second, strFilename, *pdb_temp, this->env->m_fileids[strFilename]); + CheckUniqueFileid(*env.second.lock().get(), strFilename, *pdb_temp, this->env->m_fileids[strFilename]); } pdb = pdb_temp.release(); @@ -621,7 +687,7 @@ bool BerkeleyBatch::Rewrite(BerkeleyDatabase& database, const char* pszSkip) if (database.IsDummy()) { return true; } - BerkeleyEnvironment *env = database.env; + BerkeleyEnvironment *env = database.env.get(); const std::string& strFile = database.strFile; while (true) { { @@ -752,7 +818,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database) return true; } bool ret = false; - BerkeleyEnvironment *env = database.env; + BerkeleyEnvironment *env = database.env.get(); const std::string& strFile = database.strFile; TRY_LOCK(cs_db, lockDb); if (lockDb) diff --git a/src/wallet/db.h b/src/wallet/db.h index e453d441d7..762fb83a2f 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -50,10 +50,10 @@ public: std::condition_variable_any m_db_in_use; BerkeleyEnvironment(const fs::path& env_directory); + BerkeleyEnvironment(); ~BerkeleyEnvironment(); void Reset(); - void MakeMock(); bool IsMock() const { return fMockDb; } bool IsInitialized() const { return fDbEnvInit; } bool IsDatabaseLoaded(const std::string& db_filename) const { return m_databases.find(db_filename) != m_databases.end(); } @@ -101,8 +101,11 @@ public: /** Return whether a wallet database is currently loaded. */ bool IsWalletLoaded(const fs::path& wallet_path); +/** Given a wallet directory path or legacy file path, return path to main data file in the wallet database. */ +fs::path WalletDataFilePath(const fs::path& wallet_path); + /** Get BerkeleyEnvironment and database filename given a wallet path. */ -BerkeleyEnvironment* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename); +std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& wallet_path, std::string& database_filename); /** An instance of this class represents one database. * For BerkeleyDB this is just a (env, strFile) tuple. @@ -117,17 +120,11 @@ public: } /** Create DB handle to real database */ - BerkeleyDatabase(const fs::path& wallet_path, bool mock = false) : - nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0) + BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> env, std::string filename) : + nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), strFile(std::move(filename)) { - env = GetWalletEnv(wallet_path, strFile); - auto inserted = env->m_databases.emplace(strFile, std::ref(*this)); + auto inserted = this->env->m_databases.emplace(strFile, std::ref(*this)); assert(inserted.second); - if (mock) { - env->Close(); - env->Reset(); - env->MakeMock(); - } } ~BerkeleyDatabase() { @@ -140,7 +137,8 @@ public: /** Return object for accessing database at specified path. */ static std::unique_ptr<BerkeleyDatabase> Create(const fs::path& path) { - return MakeUnique<BerkeleyDatabase>(path); + std::string filename; + return MakeUnique<BerkeleyDatabase>(GetWalletEnv(path, filename), std::move(filename)); } /** Return object for accessing dummy database with no read/write capabilities. */ @@ -152,7 +150,7 @@ public: /** Return object for accessing temporary in-memory database. */ static std::unique_ptr<BerkeleyDatabase> CreateMock() { - return MakeUnique<BerkeleyDatabase>("", true /* mock */); + return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), ""); } /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero @@ -176,12 +174,21 @@ public: unsigned int nLastFlushed; int64_t nLastWalletUpdate; + /** + * Pointer to shared database environment. + * + * Normally there is only one BerkeleyDatabase object per + * BerkeleyEnvivonment, but in the special, backwards compatible case where + * multiple wallet BDB data files are loaded from the same directory, this + * will point to a shared instance that gets freed when the last data file + * is closed. + */ + std::shared_ptr<BerkeleyEnvironment> env; + /** Database pointer. This is initialized lazily and reset during flushes, so it can be null. */ std::unique_ptr<Db> m_db; private: - /** BerkeleyDB specific */ - BerkeleyEnvironment *env; std::string strFile; /** Return whether this database handle is a dummy for testing. @@ -191,10 +198,29 @@ private: bool IsDummy() { return env == nullptr; } }; - /** RAII class that provides access to a Berkeley database */ class BerkeleyBatch { + /** RAII class that automatically cleanses its data on destruction */ + class SafeDbt final + { + Dbt m_dbt; + + public: + // construct Dbt with internally-managed data + SafeDbt(); + // construct Dbt with provided data + SafeDbt(void* data, size_t size); + ~SafeDbt(); + + // delegate to Dbt + const void* get_data() const; + u_int32_t get_size() const; + + // conversion operator to access the underlying Dbt + operator Dbt*(); + }; + protected: Db* pdb; std::string strFile; @@ -222,7 +248,6 @@ public: /* verifies the database file */ static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc); -public: template <typename K, typename T> bool Read(const K& key, T& value) { @@ -233,13 +258,11 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Read - Dbt datValue; - datValue.set_flags(DB_DBT_MALLOC); - int ret = pdb->get(activeTxn, &datKey, &datValue, 0); - memory_cleanse(datKey.get_data(), datKey.get_size()); + SafeDbt datValue; + int ret = pdb->get(activeTxn, datKey, datValue, 0); bool success = false; if (datValue.get_data() != nullptr) { // Unserialize value @@ -250,10 +273,6 @@ public: } catch (const std::exception&) { // In this case success remains 'false' } - - // Clear and free memory - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datValue.get_data()); } return ret == 0 && success; } @@ -270,20 +289,16 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Value CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue.reserve(10000); ssValue << value; - Dbt datValue(ssValue.data(), ssValue.size()); + SafeDbt datValue(ssValue.data(), ssValue.size()); // Write - int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); - - // Clear memory in case it was a private key - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); + int ret = pdb->put(activeTxn, datKey, datValue, (fOverwrite ? 0 : DB_NOOVERWRITE)); return (ret == 0); } @@ -299,13 +314,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Erase - int ret = pdb->del(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->del(activeTxn, datKey, 0); return (ret == 0 || ret == DB_NOTFOUND); } @@ -319,13 +331,10 @@ public: CDataStream ssKey(SER_DISK, CLIENT_VERSION); ssKey.reserve(1000); ssKey << key; - Dbt datKey(ssKey.data(), ssKey.size()); + SafeDbt datKey(ssKey.data(), ssKey.size()); // Exists - int ret = pdb->exists(activeTxn, &datKey, 0); - - // Clear memory - memory_cleanse(datKey.get_data(), datKey.get_size()); + int ret = pdb->exists(activeTxn, datKey, 0); return (ret == 0); } @@ -340,20 +349,12 @@ public: return pcursor; } - int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, bool setRange = false) + int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue) { // Read at cursor - Dbt datKey; - unsigned int fFlags = DB_NEXT; - if (setRange) { - datKey.set_data(ssKey.data()); - datKey.set_size(ssKey.size()); - fFlags = DB_SET_RANGE; - } - Dbt datValue; - datKey.set_flags(DB_DBT_MALLOC); - datValue.set_flags(DB_DBT_MALLOC); - int ret = pcursor->get(&datKey, &datValue, fFlags); + SafeDbt datKey; + SafeDbt datValue; + int ret = pcursor->get(datKey, datValue, DB_NEXT); if (ret != 0) return ret; else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) @@ -366,16 +367,9 @@ public: ssValue.SetType(SER_DISK); ssValue.clear(); ssValue.write((char*)datValue.get_data(), datValue.get_size()); - - // Clear and free memory - memory_cleanse(datKey.get_data(), datKey.get_size()); - memory_cleanse(datValue.get_data(), datValue.get_size()); - free(datKey.get_data()); - free(datValue.get_data()); return 0; } -public: bool TxnBegin() { if (!pdb || activeTxn) diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 7a71aea715..4ec9dca420 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -14,7 +14,9 @@ #include <validation.h> //for mempool access #include <txmempool.h> #include <util/moneystr.h> +#include <util/rbf.h> #include <util/system.h> +#include <util/validation.h> #include <net.h> //! Check whether transaction has descendant in wallet or mempool, or has been @@ -27,9 +29,7 @@ static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chai } { - LOCK(mempool.cs); - auto it_mp = mempool.mapTx.find(wtx.GetHash()); - if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) { + if (wallet->chain().hasDescendantsInMempool(wtx.GetHash())) { errors.push_back("Transaction has descendants in the mempool"); return feebumper::Result::INVALID_PARAMETER; } @@ -75,9 +75,11 @@ bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid) return res == feebumper::Result::OK; } -Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, - CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) +Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, std::vector<std::string>& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { + new_fee = total_fee; + auto locked_chain = wallet->chain().lock(); LOCK(wallet->cs_wallet); errors.clear(); @@ -121,49 +123,33 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // calculate the old fee and fee-rate old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); CFeeRate nOldFeeRate(old_fee, txSize); - CFeeRate nNewFeeRate; // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to // future proof against changes to network wide policy for incremental relay // fee that our node may not be aware of. + CFeeRate nodeIncrementalRelayFee = wallet->chain().relayIncrementalFee(); CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); - if (::incrementalRelayFee > walletIncrementalRelayFee) { - walletIncrementalRelayFee = ::incrementalRelayFee; + if (nodeIncrementalRelayFee > walletIncrementalRelayFee) { + walletIncrementalRelayFee = nodeIncrementalRelayFee; } - if (total_fee > 0) { - CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize); - if (total_fee < minTotalFee) { - errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", - FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize)))); - return Result::INVALID_PARAMETER; - } - CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize); - if (total_fee < requiredFee) { - errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", - FormatMoney(requiredFee))); - return Result::INVALID_PARAMETER; - } - new_fee = total_fee; - nNewFeeRate = CFeeRate(total_fee, maxNewTxSize); - } else { - new_fee = GetMinimumFee(*wallet, maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */); - nNewFeeRate = CFeeRate(new_fee, maxNewTxSize); - - // New fee rate must be at least old rate + minimum incremental relay rate - // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized - // in that unit (fee per kb). - // However, nOldFeeRate is a calculated value from the tx fee/size, so - // add 1 satoshi to the result, because it may have been rounded down. - if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) { - nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()); - new_fee = nNewFeeRate.GetFee(maxNewTxSize); - } + CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + nodeIncrementalRelayFee.GetFee(maxNewTxSize); + if (total_fee < minTotalFee) { + errors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", + FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(nodeIncrementalRelayFee.GetFee(maxNewTxSize)))); + return Result::INVALID_PARAMETER; + } + CAmount requiredFee = GetRequiredFee(*wallet, maxNewTxSize); + if (total_fee < requiredFee) { + errors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)", + FormatMoney(requiredFee))); + return Result::INVALID_PARAMETER; } // Check that in all cases the new fee doesn't violate maxTxFee - if (new_fee > maxTxFee) { + const CAmount max_tx_fee = wallet->chain().maxTxFee(); + if (new_fee > max_tx_fee) { errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)", - FormatMoney(new_fee), FormatMoney(maxTxFee))); + FormatMoney(new_fee), FormatMoney(max_tx_fee))); return Result::WALLET_ERROR; } @@ -172,15 +158,15 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps, // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a // moment earlier. In this case, we report an error to the user, who may use total_fee to make an adjustment. - CFeeRate minMempoolFeeRate = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + CFeeRate minMempoolFeeRate = wallet->chain().mempoolMinFee(); + CFeeRate nNewFeeRate = CFeeRate(total_fee, maxNewTxSize); if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { errors.push_back(strprintf( "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- " - "the totalFee value should be at least %s or the settxfee value should be at least %s to add transaction", + "the totalFee value should be at least %s to add transaction", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), - FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), - FormatMoney(minMempoolFeeRate.GetFeePerK()))); + FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)))); return Result::WALLET_ERROR; } @@ -197,7 +183,7 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin // If the output would become dust, discard it (converting the dust to fee) poutput->nValue -= nDelta; - if (poutput->nValue <= GetDustThreshold(*poutput, GetDiscardRate(*wallet, ::feeEstimator))) { + if (poutput->nValue <= GetDustThreshold(*poutput, GetDiscardRate(*wallet))) { wallet->WalletLogPrintf("Bumping fee and discarding dust output\n"); new_fee += poutput->nValue; mtx.vout.erase(mtx.vout.begin() + nOutput); @@ -210,6 +196,109 @@ Result CreateTransaction(const CWallet* wallet, const uint256& txid, const CCoin } } + return Result::OK; +} + + +Result CreateRateBumpTransaction(CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<std::string>& errors, + CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) +{ + // We are going to modify coin control later, copy to re-use + CCoinControl new_coin_control(coin_control); + + auto locked_chain = wallet->chain().lock(); + LOCK(wallet->cs_wallet); + errors.clear(); + auto it = wallet->mapWallet.find(txid); + if (it == wallet->mapWallet.end()) { + errors.push_back("Invalid or non-wallet transaction id"); + return Result::INVALID_ADDRESS_OR_KEY; + } + const CWalletTx& wtx = it->second; + + Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); + if (result != Result::OK) { + return result; + } + + // Fill in recipients(and preserve a single change key if there is one) + std::vector<CRecipient> recipients; + for (const auto& output : wtx.tx->vout) { + if (!wallet->IsChange(output)) { + CRecipient recipient = {output.scriptPubKey, output.nValue, false}; + recipients.push_back(recipient); + } else { + CTxDestination change_dest; + ExtractDestination(output.scriptPubKey, change_dest); + new_coin_control.destChange = change_dest; + } + } + + // Get the fee rate of the original transaction. This is calculated from + // the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the + // result. + old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); + // Feerate of thing we are bumping + CFeeRate feerate(old_fee, txSize); + feerate += CFeeRate(1); + + // The node has a configurable incremental relay fee. Increment the fee by + // the minimum of that and the wallet's conservative + // WALLET_INCREMENTAL_RELAY_FEE value to future proof against changes to + // network wide policy for incremental relay fee that our node may not be + // aware of. This ensures we're over the over the required relay fee rate + // (BIP 125 rule 4). The replacement tx will be at least as large as the + // original tx, so the total fee will be greater (BIP 125 rule 3) + CFeeRate node_incremental_relay_fee = wallet->chain().relayIncrementalFee(); + CFeeRate wallet_incremental_relay_fee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); + feerate += std::max(node_incremental_relay_fee, wallet_incremental_relay_fee); + + // Fee rate must also be at least the wallet's GetMinimumFeeRate + CFeeRate min_feerate(GetMinimumFeeRate(*wallet, new_coin_control, /* feeCalc */ nullptr)); + + // Set the required fee rate for the replacement transaction in coin control. + new_coin_control.m_feerate = std::max(feerate, min_feerate); + + // Fill in required inputs we are double-spending(all of them) + // N.B.: bip125 doesn't require all the inputs in the replaced transaction to be + // used in the replacement transaction, but it's very important for wallets to make + // sure that happens. If not, it would be possible to bump a transaction A twice to + // A2 and A3 where A2 and A3 don't conflict (or alternatively bump A to A2 and A2 + // to A3 where A and A3 don't conflict). If both later get confirmed then the sender + // has accidentally double paid. + for (const auto& inputs : wtx.tx->vin) { + new_coin_control.Select(COutPoint(inputs.prevout)); + } + new_coin_control.fAllowOtherInputs = true; + + // We cannot source new unconfirmed inputs(bip125 rule 2) + new_coin_control.m_min_depth = 1; + + CTransactionRef tx_new = MakeTransactionRef(); + CReserveKey reservekey(wallet); + CAmount fee_ret; + int change_pos_in_out = -1; // No requested location for change + std::string fail_reason; + if (!wallet->CreateTransaction(*locked_chain, recipients, tx_new, reservekey, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { + errors.push_back("Unable to create transaction: " + fail_reason); + return Result::WALLET_ERROR; + } + + // If change key hasn't been ReturnKey'ed by this point, we take it out of keypool + reservekey.KeepKey(); + + // Write back new fee if successful + new_fee = fee_ret; + + // Write back transaction + mtx = CMutableTransaction(*tx_new); + // Mark new tx not replaceable, if requested. + if (!coin_control.m_signal_bip125_rbf.get_value_or(wallet->m_signal_rbf)) { + for (auto& input : mtx.vin) { + if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; + } + } return Result::OK; } @@ -247,7 +336,7 @@ Result CommitTransaction(CWallet* wallet, const uint256& txid, CMutableTransacti CReserveKey reservekey(wallet); CValidationState state; - if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekey, g_connman.get(), state)) { + if (!wallet->CommitTransaction(tx, std::move(mapValue), oldWtx.vOrderForm, reservekey, state)) { // NOTE: CommitTransaction never returns false, so this should never happen. errors.push_back(strprintf("The transaction was rejected: %s", FormatStateMessage(state))); return Result::WALLET_ERROR; diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index a1eb5d0e0d..f9cbfc5f68 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -28,8 +28,8 @@ enum class Result //! Return whether transaction can be bumped. bool TransactionCanBeBumped(const CWallet* wallet, const uint256& txid); -//! Create bumpfee transaction. -Result CreateTransaction(const CWallet* wallet, +//! Create bumpfee transaction based on total amount. +Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, const CCoinControl& coin_control, CAmount total_fee, @@ -38,6 +38,15 @@ Result CreateTransaction(const CWallet* wallet, CAmount& new_fee, CMutableTransaction& mtx); +//! Create bumpfee transaction based on feerate estimates. +Result CreateRateBumpTransaction(CWallet* wallet, + const uint256& txid, + const CCoinControl& coin_control, + std::vector<std::string>& errors, + CAmount& old_fee, + CAmount& new_fee, + CMutableTransaction& mtx); + //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was //! impossible to create the signature(s) diff --git a/src/wallet/fees.cpp b/src/wallet/fees.cpp index 9e2984ff05..560c86a70a 100644 --- a/src/wallet/fees.cpp +++ b/src/wallet/fees.cpp @@ -6,7 +6,6 @@ #include <wallet/fees.h> #include <policy/policy.h> -#include <txmempool.h> #include <util/system.h> #include <validation.h> #include <wallet/coincontrol.h> @@ -19,12 +18,13 @@ CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes) } -CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc) +CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, FeeCalculation* feeCalc) { - CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, pool, estimator, feeCalc).GetFee(nTxBytes); + CAmount fee_needed = GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes); // Always obey the maximum - if (fee_needed > maxTxFee) { - fee_needed = maxTxFee; + const CAmount max_tx_fee = wallet.chain().maxTxFee(); + if (fee_needed > max_tx_fee) { + fee_needed = max_tx_fee; if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; } return fee_needed; @@ -32,10 +32,10 @@ CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinC CFeeRate GetRequiredFeeRate(const CWallet& wallet) { - return std::max(wallet.m_min_fee, ::minRelayTxFee); + return std::max(wallet.m_min_fee, wallet.chain().relayMinFee()); } -CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc) +CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, FeeCalculation* feeCalc) { /* User control of how to calculate fee uses the following parameter precedence: 1. coin_control.m_feerate @@ -64,7 +64,7 @@ CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_contr if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true; else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false; - feerate_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate); + feerate_needed = wallet.chain().estimateSmartFee(target, conservative_estimate, feeCalc); if (feerate_needed == CFeeRate(0)) { // if we don't have enough data for estimateSmartFee, then use fallback fee feerate_needed = wallet.m_fallback_fee; @@ -74,7 +74,7 @@ CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_contr if (wallet.m_fallback_fee == CFeeRate(0)) return feerate_needed; } // Obey mempool min fee when using smart fee estimation - CFeeRate min_mempool_feerate = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + CFeeRate min_mempool_feerate = wallet.chain().mempoolMinFee(); if (feerate_needed < min_mempool_feerate) { feerate_needed = min_mempool_feerate; if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN; @@ -90,13 +90,13 @@ CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_contr return feerate_needed; } -CFeeRate GetDiscardRate(const CWallet& wallet, const CBlockPolicyEstimator& estimator) +CFeeRate GetDiscardRate(const CWallet& wallet) { - unsigned int highest_target = estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); - CFeeRate discard_rate = estimator.estimateSmartFee(highest_target, nullptr /* FeeCalculation */, false /* conservative */); + unsigned int highest_target = wallet.chain().estimateMaxBlocks(); + CFeeRate discard_rate = wallet.chain().estimateSmartFee(highest_target, false /* conservative */); // Don't let discard_rate be greater than longest possible fee estimate if we get a valid fee estimate discard_rate = (discard_rate == CFeeRate(0)) ? wallet.m_discard_rate : std::min(discard_rate, wallet.m_discard_rate); // Discard rate must be at least dustRelayFee - discard_rate = std::max(discard_rate, ::dustRelayFee); + discard_rate = std::max(discard_rate, wallet.chain().relayDustFee()); return discard_rate; } diff --git a/src/wallet/fees.h b/src/wallet/fees.h index 6bfee456c0..434f211dc2 100644 --- a/src/wallet/fees.h +++ b/src/wallet/fees.h @@ -8,10 +8,8 @@ #include <amount.h> -class CBlockPolicyEstimator; class CCoinControl; class CFeeRate; -class CTxMemPool; class CWallet; struct FeeCalculation; @@ -25,7 +23,7 @@ CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes); * Estimate the minimum fee considering user set parameters * and the required fee */ -CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc); +CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, FeeCalculation* feeCalc); /** * Return the minimum required feerate taking into account the @@ -37,11 +35,11 @@ CFeeRate GetRequiredFeeRate(const CWallet& wallet); * Estimate the minimum fee rate considering user set parameters * and the required fee */ -CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation* feeCalc); +CFeeRate GetMinimumFeeRate(const CWallet& wallet, const CCoinControl& coin_control, FeeCalculation* feeCalc); /** * Return the maximum feerate for discarding change. */ -CFeeRate GetDiscardRate(const CWallet& wallet, const CBlockPolicyEstimator& estimator); +CFeeRate GetDiscardRate(const CWallet& wallet); #endif // BITCOIN_WALLET_FEES_H diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 14d811c6cd..e7eea94e06 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -9,6 +9,7 @@ #include <net.h> #include <scheduler.h> #include <outputtype.h> +#include <util/error.h> #include <util/system.h> #include <util/moneystr.h> #include <validation.h> @@ -127,69 +128,6 @@ bool WalletInit::ParameterInteraction() const InitWarning(AmountHighWarn("-minrelaytxfee") + " " + _("The wallet will avoid paying less than the minimum relay fee.")); - if (gArgs.IsArgSet("-maxtxfee")) - { - CAmount nMaxFee = 0; - if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) - return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); - if (nMaxFee > HIGH_MAX_TX_FEE) - InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); - maxTxFee = nMaxFee; - if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee) - { - return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), - gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString())); - } - } - - return true; -} - -bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) -{ - if (gArgs.IsArgSet("-walletdir")) { - fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); - boost::system::error_code error; - // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory - fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); - if (error || !fs::exists(wallet_dir)) { - return InitError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); - } else if (!fs::is_directory(wallet_dir)) { - return InitError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); - // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version - } else if (!wallet_dir.is_absolute()) { - return InitError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); - } - gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); - } - - LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); - - uiInterface.InitMessage(_("Verifying wallet(s)...")); - - // Parameter interaction code should have thrown an error if -salvagewallet - // was enabled with more than wallet file, so the wallet_files size check - // here should have no effect. - bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; - - // Keep track of each wallet absolute path to detect duplicates. - std::set<fs::path> wallet_paths; - - for (const auto& wallet_file : wallet_files) { - WalletLocation location(wallet_file); - - if (!wallet_paths.insert(location.GetPath()).second) { - return InitError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); - } - - std::string error_string; - std::string warning_string; - bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); - if (!error_string.empty()) InitError(error_string); - if (!warning_string.empty()) InitWarning(warning_string); - if (!verify_success) return false; - } - return true; } @@ -202,47 +140,3 @@ void WalletInit::Construct(InitInterfaces& interfaces) const gArgs.SoftSetArg("-wallet", ""); interfaces.chain_clients.emplace_back(interfaces::MakeWalletClient(*interfaces.chain, gArgs.GetArgs("-wallet"))); } - -bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) -{ - for (const std::string& walletFile : wallet_files) { - std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); - if (!pwallet) { - return false; - } - AddWallet(pwallet); - } - - return true; -} - -void StartWallets(CScheduler& scheduler) -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->postInitProcess(); - } - - // Run a thread to flush wallet periodically - scheduler.scheduleEvery(MaybeCompactWalletDB, 500); -} - -void FlushWallets() -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->Flush(false); - } -} - -void StopWallets() -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - pwallet->Flush(true); - } -} - -void UnloadWallets() -{ - for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { - RemoveWallet(pwallet); - } -} diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp new file mode 100644 index 0000000000..79c5f439df --- /dev/null +++ b/src/wallet/load.cpp @@ -0,0 +1,112 @@ +// 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/load.h> + +#include <interfaces/chain.h> +#include <scheduler.h> +#include <util/system.h> +#include <wallet/wallet.h> + +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + if (gArgs.IsArgSet("-walletdir")) { + fs::path wallet_dir = gArgs.GetArg("-walletdir", ""); + boost::system::error_code error; + // The canonical path cleans the path, preventing >1 Berkeley environment instances for the same directory + fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error); + if (error || !fs::exists(wallet_dir)) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" does not exist"), wallet_dir.string())); + return false; + } else if (!fs::is_directory(wallet_dir)) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" is not a directory"), wallet_dir.string())); + return false; + // The canonical path transforms relative paths into absolute ones, so we check the non-canonical version + } else if (!wallet_dir.is_absolute()) { + chain.initError(strprintf(_("Specified -walletdir \"%s\" is a relative path"), wallet_dir.string())); + return false; + } + gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string()); + } + + LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); + + chain.initMessage(_("Verifying wallet(s)...")); + + // Parameter interaction code should have thrown an error if -salvagewallet + // was enabled with more than wallet file, so the wallet_files size check + // here should have no effect. + bool salvage_wallet = gArgs.GetBoolArg("-salvagewallet", false) && wallet_files.size() <= 1; + + // Keep track of each wallet absolute path to detect duplicates. + std::set<fs::path> wallet_paths; + + for (const auto& wallet_file : wallet_files) { + WalletLocation location(wallet_file); + + if (!wallet_paths.insert(location.GetPath()).second) { + chain.initError(strprintf(_("Error loading wallet %s. Duplicate -wallet filename specified."), wallet_file)); + return false; + } + + std::string error_string; + std::string warning_string; + bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warning_string); + if (!error_string.empty()) chain.initError(error_string); + if (!warning_string.empty()) chain.initWarning(warning_string); + if (!verify_success) return false; + } + + return true; +} + +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files) +{ + for (const std::string& walletFile : wallet_files) { + std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile)); + if (!pwallet) { + return false; + } + AddWallet(pwallet); + } + + return true; +} + +void StartWallets(CScheduler& scheduler) +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->postInitProcess(); + } + + // Schedule periodic wallet flushes and tx rebroadcasts + scheduler.scheduleEvery(MaybeCompactWalletDB, 500); + scheduler.scheduleEvery(MaybeResendWalletTxs, 1000); +} + +void FlushWallets() +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->Flush(false); + } +} + +void StopWallets() +{ + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->Flush(true); + } +} + +void UnloadWallets() +{ + auto wallets = GetWallets(); + while (!wallets.empty()) { + auto wallet = wallets.back(); + wallets.pop_back(); + RemoveWallet(wallet); + UnloadWallet(std::move(wallet)); + } +} diff --git a/src/wallet/load.h b/src/wallet/load.h new file mode 100644 index 0000000000..9bb6f2166e --- /dev/null +++ b/src/wallet/load.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef BITCOIN_WALLET_LOAD_H +#define BITCOIN_WALLET_LOAD_H + +#include <string> +#include <vector> + +class CScheduler; + +namespace interfaces { +class Chain; +} // namespace interfaces + +//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. +//! This function will perform salvage on the wallet if requested, as long as only one wallet is +//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). +bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Load wallet databases. +bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); + +//! Complete startup of wallets. +void StartWallets(CScheduler& scheduler); + +//! Flush all wallets in preparation for shutdown. +void FlushWallets(); + +//! Stop all wallets. Wallets will be flushed first. +void StopWallets(); + +//! Close all wallets. +void UnloadWallets(); + +#endif // BITCOIN_WALLET_LOAD_H diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp new file mode 100644 index 0000000000..1b17b09763 --- /dev/null +++ b/src/wallet/psbtwallet.cpp @@ -0,0 +1,60 @@ +// 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/psbtwallet.h> + +TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs) +{ + LOCK(pwallet->cs_wallet); + // Get all of the previous transactions + complete = true; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + const CTxIn& txin = psbtx.tx->vin[i]; + PSBTInput& input = psbtx.inputs.at(i); + + if (PSBTInputSigned(input)) { + continue; + } + + // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness. + if (!input.IsSane()) { + return TransactionError::INVALID_PSBT; + } + + // If we have no utxo, grab it from the wallet. + if (!input.non_witness_utxo && input.witness_utxo.IsNull()) { + const uint256& txhash = txin.prevout.hash; + const auto it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + // We only need the non_witness_utxo, which is a superset of the witness_utxo. + // The signing code will switch to the smaller witness_utxo if this is ok. + input.non_witness_utxo = wtx.tx; + } + } + + // Get the Sighash type + if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + return TransactionError::SIGHASH_MISMATCH; + } + + complete &= SignPSBTInput(HidingSigningProvider(pwallet, !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) { + const CTxOut& out = psbtx.tx->vout.at(i); + PSBTOutput& psbt_out = psbtx.outputs.at(i); + + // Fill a SignatureData with output info + SignatureData sigdata; + psbt_out.FillSignatureData(sigdata); + + MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); + ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata); + psbt_out.FromSignatureData(sigdata); + } + + return TransactionError::OK; +} diff --git a/src/wallet/psbtwallet.h b/src/wallet/psbtwallet.h new file mode 100644 index 0000000000..a24a0967d2 --- /dev/null +++ b/src/wallet/psbtwallet.h @@ -0,0 +1,34 @@ +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_PSBTWALLET_H +#define BITCOIN_WALLET_PSBTWALLET_H + +#include <node/transaction.h> +#include <psbt.h> +#include <primitives/transaction.h> +#include <wallet/wallet.h> + +/** + * Fills out a PSBT with information from the wallet. Fills in UTXOs if we have + * them. Tries to sign if sign=true. Sets `complete` if the PSBT is now complete + * (i.e. has all required signatures or signature-parts, and is ready to + * finalize.) Sets `error` and returns false if something goes wrong. + * + * @param[in] pwallet pointer to a wallet + * @param[in] &psbtx reference to PartiallySignedTransaction to fill in + * @param[out] &complete indicates whether the PSBT is now complete + * @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify) + * @param[in] sign whether to sign or not + * @param[in] bip32derivs whether to fill in bip32 derivation information if available + * return error + */ +NODISCARD TransactionError FillPSBT(const CWallet* pwallet, + PartiallySignedTransaction& psbtx, + bool& complete, + int sighash_type = 1 /* SIGHASH_ALL */, + bool sign = true, + bool bip32derivs = false); + +#endif // BITCOIN_WALLET_PSBTWALLET_H diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index ed1e2d3940..c339e111ba 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -9,9 +9,11 @@ #include <merkleblock.h> #include <rpc/server.h> #include <rpc/util.h> +#include <script/descriptor.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> +#include <util/bip32.h> #include <util/system.h> #include <util/time.h> #include <validation.h> @@ -66,7 +68,7 @@ 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) +static bool GetWalletAddressesForKey(CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { bool fLabelFound = false; CKey key; @@ -111,16 +113,16 @@ UniValue importprivkey(const JSONRPCRequest& request) throw std::runtime_error( RPCHelpMan{"importprivkey", "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" - "Hint: use importmulti to import more than one private key.\n", - { - {"privkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The private key (see dumpprivkey)"}, - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "current label if address exists, otherwise \"\"", "An optional label"}, - {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, - }} - .ToString() + + "Hint: use importmulti to import more than one private key.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "\nExamples:\n" + "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n", + { + {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"}, + {"label", RPCArg::Type::STR, /* default */ "current label if address exists, otherwise \"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, + }, + RPCResults{}, + RPCExamples{ "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" @@ -131,8 +133,12 @@ UniValue importprivkey(const JSONRPCRequest& request) + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") - ); + }, + }.ToString()); + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); + } WalletRescanReserver reserver(pwallet); bool fRescan = true; @@ -151,8 +157,9 @@ UniValue importprivkey(const JSONRPCRequest& request) if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); - if (fRescan && fPruneMode) + if (fRescan && pwallet->chain().getPruneMode()) { throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + } if (fRescan && !reserver.reserve()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); @@ -209,16 +216,18 @@ UniValue abortrescan(const JSONRPCRequest& request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"abortrescan", - "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n", {}} - .ToString() + - "\nExamples:\n" + "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n", + {}, + RPCResults{}, + RPCExamples{ "\nImport a private key\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nAbort the running wallet rescan\n" + HelpExampleCli("abortrescan", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "") - ); + }, + }.ToString()); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false; pwallet->AbortRescan(); @@ -272,27 +281,28 @@ UniValue importaddress(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) throw std::runtime_error( RPCHelpMan{"importaddress", - "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n", - { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The Bitcoin address (or hex-encoded script)"}, - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, - {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, - {"p2sh", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Add the P2SH version of the script as well"}, - }} - .ToString() + + "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" "If you have the full public key, you should call importpubkey instead of this.\n" "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" - "as change, and not show up in many RPCs.\n" - "\nExamples:\n" + "as change, and not show up in many RPCs.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, + {"label", RPCArg::Type::STR, /* default */ "\"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, + {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Add the P2SH version of the script as well"}, + }, + RPCResults{}, + RPCExamples{ "\nImport an address with rescan\n" + HelpExampleCli("importaddress", "\"myaddress\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") - ); + }, + }.ToString()); std::string strLabel; @@ -304,8 +314,9 @@ UniValue importaddress(const JSONRPCRequest& request) if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); - if (fRescan && fPruneMode) + if (fRescan && pwallet->chain().getPruneMode()) { throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + } WalletRescanReserver reserver(pwallet); if (fRescan && !reserver.reserve()) { @@ -337,7 +348,11 @@ UniValue importaddress(const JSONRPCRequest& request) if (fRescan) { RescanWallet(*pwallet, reserver); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } } return NullUniValue; @@ -356,10 +371,12 @@ UniValue importprunedfunds(const JSONRPCRequest& request) RPCHelpMan{"importprunedfunds", "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n", { - {"rawtransaction", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A raw transaction in hex funding an already-existing address in wallet"}, - {"txoutproof", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex output from gettxoutproof that contains the transaction"}, - }} - .ToString() + {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"}, + {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"}, + }, + RPCResults{}, + RPCExamples{""}, + }.ToString() ); CMutableTransaction tx; @@ -379,8 +396,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request) if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { auto locked_chain = pwallet->chain().lock(); - const CBlockIndex* pindex = LookupBlockIndex(merkleBlock.header.GetHash()); - if (!pindex || !chainActive.Contains(pindex)) { + if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } @@ -422,14 +438,15 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) RPCHelpMan{"removeprunedfunds", "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex-encoded id of the transaction you are deleting"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -461,23 +478,24 @@ UniValue importpubkey(const JSONRPCRequest& request) if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) throw std::runtime_error( RPCHelpMan{"importpubkey", - "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n", - { - {"pubkey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The hex-encoded public key"}, - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "\"\"", "An optional label"}, - {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Rescan the wallet for transactions"}, - }} - .ToString() + + "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" - "\nExamples:\n" + "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n", + { + {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"}, + {"label", RPCArg::Type::STR, /* default */ "\"\"", "An optional label"}, + {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, + }, + RPCResults{}, + RPCExamples{ "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") - ); + }, + }.ToString()); std::string strLabel; @@ -489,8 +507,9 @@ UniValue importpubkey(const JSONRPCRequest& request) if (!request.params[2].isNull()) fRescan = request.params[2].get_bool(); - if (fRescan && fPruneMode) + if (fRescan && pwallet->chain().getPruneMode()) { throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); + } WalletRescanReserver reserver(pwallet); if (fRescan && !reserver.reserve()) { @@ -517,7 +536,11 @@ UniValue importpubkey(const JSONRPCRequest& request) if (fRescan) { RescanWallet(*pwallet, reserver); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } } return NullUniValue; @@ -537,20 +560,22 @@ UniValue importwallet(const JSONRPCRequest& request) RPCHelpMan{"importwallet", "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n", { - {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet file"}, - }} - .ToString() + - "\nExamples:\n" + {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"}, + }, + RPCResults{}, + RPCExamples{ "\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"") - ); + }, + }.ToString()); - if (fPruneMode) + if (pwallet->chain().getPruneMode()) { throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); + } WalletRescanReserver reserver(pwallet); if (!reserver.reserve()) { @@ -570,16 +595,19 @@ UniValue importwallet(const JSONRPCRequest& request) if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } - nTimeBegin = chainActive.Tip()->GetBlockTime(); + Optional<int> tip_height = locked_chain->getHeight(); + nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0; int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); file.seekg(0, file.beg); // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. - uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI + pwallet->chain().showProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI + std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; + std::vector<std::pair<CScript, int64_t>> scripts; while (file.good()) { - uiInterface.ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); + pwallet->chain().showProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); std::string line; std::getline(file, line); if (line.empty() || line[0] == '#') @@ -591,13 +619,6 @@ UniValue importwallet(const JSONRPCRequest& request) continue; CKey key = DecodeSecret(vstr[0]); if (key.IsValid()) { - CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); - CKeyID keyid = pubkey.GetID(); - if (pwallet->HaveKey(keyid)) { - pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); - continue; - } int64_t nTime = DecodeDumpTime(vstr[1]); std::string strLabel; bool fLabel = true; @@ -613,40 +634,71 @@ UniValue importwallet(const JSONRPCRequest& request) fLabel = true; } } - pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid)); - if (!pwallet->AddKeyPubKey(key, pubkey)) { - fGood = false; - continue; - } - pwallet->mapKeyMetadata[keyid].nCreateTime = nTime; - if (fLabel) - pwallet->SetAddressBook(keyid, strLabel, "receive"); - nTimeBegin = std::min(nTimeBegin, nTime); + keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel)); } else if(IsHex(vstr[0])) { - std::vector<unsigned char> vData(ParseHex(vstr[0])); - CScript script = CScript(vData.begin(), vData.end()); - CScriptID id(script); - if (pwallet->HaveCScript(id)) { - pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", vstr[0]); - continue; - } - if(!pwallet->AddCScript(script)) { - pwallet->WalletLogPrintf("Error importing script %s\n", vstr[0]); - fGood = false; - continue; - } - int64_t birth_time = DecodeDumpTime(vstr[1]); - if (birth_time > 0) { - pwallet->m_script_metadata[id].nCreateTime = birth_time; - nTimeBegin = std::min(nTimeBegin, birth_time); - } + std::vector<unsigned char> vData(ParseHex(vstr[0])); + CScript script = CScript(vData.begin(), vData.end()); + int64_t birth_time = DecodeDumpTime(vstr[1]); + scripts.push_back(std::pair<CScript, int64_t>(script, birth_time)); } } file.close(); - uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI + // We now know whether we are importing private keys, so we can error if private keys are disabled + if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI + throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled"); + } + double total = (double)(keys.size() + scripts.size()); + double progress = 0; + for (const auto& key_tuple : keys) { + pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); + const CKey& key = std::get<0>(key_tuple); + int64_t time = std::get<1>(key_tuple); + bool has_label = std::get<2>(key_tuple); + std::string label = std::get<3>(key_tuple); + + CPubKey pubkey = key.GetPubKey(); + assert(key.VerifyPubKey(pubkey)); + CKeyID keyid = pubkey.GetID(); + if (pwallet->HaveKey(keyid)) { + pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid)); + continue; + } + pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid)); + if (!pwallet->AddKeyPubKey(key, pubkey)) { + fGood = false; + continue; + } + pwallet->mapKeyMetadata[keyid].nCreateTime = time; + if (has_label) + pwallet->SetAddressBook(keyid, label, "receive"); + nTimeBegin = std::min(nTimeBegin, time); + progress++; + } + for (const auto& script_pair : scripts) { + pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); + const CScript& script = script_pair.first; + int64_t time = script_pair.second; + CScriptID id(script); + if (pwallet->HaveCScript(id)) { + pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", HexStr(script)); + continue; + } + if(!pwallet->AddCScript(script)) { + pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); + fGood = false; + continue; + } + if (time > 0) { + pwallet->m_script_metadata[id].nCreateTime = time; + nTimeBegin = std::min(nTimeBegin, time); + } + progress++; + } + pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI pwallet->UpdateTimeFirstKey(nTimeBegin); } - uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI + pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); pwallet->MarkDirty(); @@ -670,16 +722,17 @@ UniValue dumpprivkey(const JSONRPCRequest& request) "\nReveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for the private key"}, - }} - .ToString() + - "\nResult:\n" + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"}, + }, + RPCResult{ "\"key\" (string) The private key\n" - "\nExamples:\n" - + HelpExampleCli("dumpprivkey", "\"myaddress\"") + }, + RPCExamples{ + HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -719,17 +772,18 @@ UniValue dumpwallet(const JSONRPCRequest& request) "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n", { - {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The filename with path (either absolute or relative to bitcoind)"}, - }} - .ToString() + - "\nResult:\n" + {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (either absolute or relative to bitcoind)"}, + }, + RPCResult{ "{ (json object)\n" " \"filename\" : { (string) The filename with full absolute path\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("dumpwallet", "\"test\"") + }, + RPCExamples{ + HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -773,8 +827,9 @@ UniValue dumpwallet(const JSONRPCRequest& request) // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); - file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString()); - file << strprintf("# mined on %s\n", FormatISO8601DateTime(chainActive.Tip()->GetBlockTime())); + const Optional<int> tip_height = locked_chain->getHeight(); + file << strprintf("# * Best block at time of backup was %i (%s),\n", tip_height.get_value_or(-1), tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : "(missing block hash)"); + file << strprintf("# mined on %s\n", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : "(missing block time)"); file << "\n"; // add the base58check encoded extended master if the wallet uses HD @@ -808,7 +863,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].hdKeypath.size() > 0 ? " hdkeypath="+pwallet->mapKeyMetadata[keyid].hdKeypath : "")); + file << strprintf(" # addr=%s%s\n", strAddr, (pwallet->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(pwallet->mapKeyMetadata[keyid].key_origin.path) : "")); } } file << "\n"; @@ -845,13 +900,14 @@ struct ImportData // Output data std::set<CScript> import_scripts; std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) + std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins; }; enum class ScriptContext { - TOP, //! Top-level scriptPubKey - P2SH, //! P2SH redeemScript - WITNESS_V0, //! P2WSH witnessScript + TOP, //!< Top-level scriptPubKey + P2SH, //!< P2SH redeemScript + WITNESS_V0, //!< P2WSH witnessScript }; // Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. @@ -923,154 +979,284 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d } } -static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) { UniValue warnings(UniValue::VARR); - UniValue result(UniValue::VOBJ); - try { - // First ensure scriptPubKey has either a script or JSON with "address" string - const UniValue& scriptPubKey = data["scriptPubKey"]; - bool isScript = scriptPubKey.getType() == UniValue::VSTR; - if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string"); + // First ensure scriptPubKey has either a script or JSON with "address" string + const UniValue& scriptPubKey = data["scriptPubKey"]; + bool isScript = scriptPubKey.getType() == UniValue::VSTR; + if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string"); + } + const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + + // Optional fields. + const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; + const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : ""; + const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); + const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); + const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; + const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; + + if (data.exists("range")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import"); + } + + // Generate the script and destination for the scriptPubKey provided + CScript script; + if (!isScript) { + CTxDestination dest = DecodeDestination(output); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } - const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); + script = GetScriptForDestination(dest); + } else { + if (!IsHex(output)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); + } + std::vector<unsigned char> vData(ParseHex(output)); + script = CScript(vData.begin(), vData.end()); + CTxDestination dest; + if (!ExtractDestination(script, dest) && !internal) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports."); + } + } + script_pub_keys.emplace(script); - // Optional fields. - const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; - const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : ""; - const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); - const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; - const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; - const std::string& label = data.exists("label") ? data["label"].get_str() : ""; + // Parse all arguments + if (strRedeemScript.size()) { + if (!IsHex(strRedeemScript)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); + } + auto parsed_redeemscript = ParseHex(strRedeemScript); + import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); + } + if (witness_script_hex.size()) { + if (!IsHex(witness_script_hex)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); + } + auto parsed_witnessscript = ParseHex(witness_script_hex); + import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); + } + for (size_t i = 0; i < pubKeys.size(); ++i) { + const auto& str = pubKeys[i].get_str(); + if (!IsHex(str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); + } + auto parsed_pubkey = ParseHex(str); + CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); + } + pubkey_map.emplace(pubkey.GetID(), pubkey); + ordered_pubkeys.push_back(pubkey.GetID()); + } + for (size_t i = 0; i < keys.size(); ++i) { + const auto& str = keys[i].get_str(); + CKey key = DecodeSecret(str); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + CPubKey pubkey = key.GetPubKey(); + CKeyID id = pubkey.GetID(); + if (pubkey_map.count(id)) { + pubkey_map.erase(id); + } + privkey_map.emplace(id, key); + } - // Generate the script and destination for the scriptPubKey provided - CScript script; - CTxDestination dest; - if (!isScript) { - dest = DecodeDestination(output); - if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); + + // Verify and process input data + have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); + if (have_solving_data) { + // Match up data in import_data with the scriptPubKey in script. + auto error = RecurseImportData(script, import_data, ScriptContext::TOP); + + // Verify whether the watchonly option corresponds to the availability of private keys. + bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); + if (!watchOnly && !spendable) { + warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); + } + if (watchOnly && spendable) { + warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); + } + + // Check that all required keys for solvability are provided. + if (error.empty()) { + for (const auto& require_key : import_data.used_keys) { + if (!require_key.second) continue; // Not a required key + if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { + error = "some required keys are missing"; + } } - script = GetScriptForDestination(dest); + } + + if (!error.empty()) { + warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); + import_data = ImportData(); + pubkey_map.clear(); + privkey_map.clear(); + have_solving_data = false; } else { - if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); + // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. + if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); + if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); + for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { + auto oldit = it++; + if (import_data.used_keys.count(oldit->first) == 0) { + warnings.push_back("Ignoring irrelevant private key."); + privkey_map.erase(oldit); + } } - std::vector<unsigned char> vData(ParseHex(output)); - script = CScript(vData.begin(), vData.end()); - if (!ExtractDestination(script, dest) && !internal) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports."); + for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { + auto oldit = it++; + auto key_data_it = import_data.used_keys.find(oldit->first); + if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { + warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); + pubkey_map.erase(oldit); + } } } + } - // Parse all arguments - ImportData import_data; - if (strRedeemScript.size()) { - if (!IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); - } - auto parsed_redeemscript = ParseHex(strRedeemScript); - import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); + return warnings; +} + +static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) +{ + UniValue warnings(UniValue::VARR); + + const std::string& descriptor = data["desc"].get_str(); + FlatSigningProvider keys; + auto parsed_desc = Parse(descriptor, keys, /* require_checksum = */ true); + if (!parsed_desc) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid"); + } + + have_solving_data = parsed_desc->IsSolvable(); + const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; + + int64_t range_start = 0, range_end = 0; + if (!parsed_desc->IsRange() && data.exists("range")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); + } else if (parsed_desc->IsRange()) { + if (!data.exists("range")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range"); } - if (witness_script_hex.size()) { - if (!IsHex(witness_script_hex)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); - } - auto parsed_witnessscript = ParseHex(witness_script_hex); - import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); + auto range = ParseRange(data["range"]); + range_start = range.first; + range_end = range.second; + if (range_start < 0 || (range_end >> 31) != 0 || range_end - range_start >= 1000000) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid descriptor range specified"); } - std::map<CKeyID, CPubKey> pubkey_map; - for (size_t i = 0; i < pubKeys.size(); ++i) { - const auto& str = pubKeys[i].get_str(); - if (!IsHex(str)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); - } - auto parsed_pubkey = ParseHex(str); - CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); - } - pubkey_map.emplace(pubkey.GetID(), pubkey); + } + + const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); + + // Expand all descriptors to get public keys and scripts. + // TODO: get private keys from descriptors too + for (int i = range_start; i <= range_end; ++i) { + FlatSigningProvider out_keys; + std::vector<CScript> scripts_temp; + parsed_desc->Expand(i, keys, scripts_temp, out_keys); + std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); + for (const auto& key_pair : out_keys.pubkeys) { + ordered_pubkeys.push_back(key_pair.first); } - std::map<CKeyID, CKey> privkey_map; - for (size_t i = 0; i < keys.size(); ++i) { - const auto& str = keys[i].get_str(); - CKey key = DecodeSecret(str); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - CPubKey pubkey = key.GetPubKey(); - CKeyID id = pubkey.GetID(); - if (pubkey_map.count(id)) { - pubkey_map.erase(id); - } + + for (const auto& x : out_keys.scripts) { + import_data.import_scripts.emplace(x.second); + } + + std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); + import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); + } + + for (size_t i = 0; i < priv_keys.size(); ++i) { + const auto& str = priv_keys[i].get_str(); + CKey key = DecodeSecret(str); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + } + CPubKey pubkey = key.GetPubKey(); + CKeyID id = pubkey.GetID(); + + // Check if this private key corresponds to a public key from the descriptor + if (!pubkey_map.count(id)) { + warnings.push_back("Ignoring irrelevant private key."); + } else { privkey_map.emplace(id, key); } + } + + // Check if all the public keys have corresponding private keys in the import for spendability. + // This does not take into account threshold multisigs which could be spendable without all keys. + // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are, + // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check. + bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(), + [&](const std::pair<CKeyID, CPubKey>& used_key) { + return privkey_map.count(used_key.first) > 0; + }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(), + [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) { + return privkey_map.count(entry.first) > 0; + }); + if (!watch_only && !spendable) { + warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); + } + if (watch_only && spendable) { + warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); + } + + return warnings; +} +static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +{ + UniValue warnings(UniValue::VARR); + UniValue result(UniValue::VOBJ); + + try { + const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; // Internal addresses should not have a label if (internal && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } + const std::string& label = data.exists("label") ? data["label"].get_str() : ""; + const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; - // Verify and process input data - bool have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); - if (have_solving_data) { - // Match up data in import_data with the scriptPubKey in script. - auto error = RecurseImportData(script, import_data, ScriptContext::TOP); - - // Verify whether the watchonly option corresponds to the availability of private keys. - bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); - if (!watchOnly && !spendable) { - warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); - } - if (watchOnly && spendable) { - warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); - } + // Add to keypool only works with privkeys disabled + if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); + } - // Check that all required keys for solvability are provided. - if (error.empty()) { - for (const auto& require_key : import_data.used_keys) { - if (!require_key.second) continue; // Not a required key - if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { - error = "some required keys are missing"; - } - } - } + ImportData import_data; + std::map<CKeyID, CPubKey> pubkey_map; + std::map<CKeyID, CKey> privkey_map; + std::set<CScript> script_pub_keys; + std::vector<CKeyID> ordered_pubkeys; + bool have_solving_data; + + if (data.exists("scriptPubKey") && data.exists("desc")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided."); + } else if (data.exists("scriptPubKey")) { + warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); + } else if (data.exists("desc")) { + warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided."); + } - if (!error.empty()) { - warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); - import_data = ImportData(); - pubkey_map.clear(); - privkey_map.clear(); - have_solving_data = false; - } else { - // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. - if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); - if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); - for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { - auto oldit = it++; - if (import_data.used_keys.count(oldit->first) == 0) { - warnings.push_back("Ignoring irrelevant private key."); - privkey_map.erase(oldit); - } - } - for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { - auto oldit = it++; - auto key_data_it = import_data.used_keys.find(oldit->first); - if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { - warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); - pubkey_map.erase(oldit); - } - } - } + // If private keys are disabled, abort if private keys are being imported + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } // Check whether we have any work to do - if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + for (const CScript& script : script_pub_keys) { + if (::IsMine(*pwallet, 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()) + "\")"); + } } // All good, time to import @@ -1078,36 +1264,52 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con for (const auto& entry : import_data.import_scripts) { if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); - } + } + } + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); + } + pwallet->UpdateTimeFirstKey(timestamp); } - for (const auto& entry : privkey_map) { - const CKey& key = entry.second; - CPubKey pubkey = key.GetPubKey(); - const CKeyID& id = entry.first; - assert(key.VerifyPubKey(pubkey)); - pwallet->mapKeyMetadata[id].nCreateTime = timestamp; - // If the private key is not present in the wallet, insert it. - if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); - } - pwallet->UpdateTimeFirstKey(timestamp); + for (const auto& entry : import_data.key_origins) { + pwallet->AddKeyOrigin(entry.second.first, entry.second.second); } - for (const auto& entry : pubkey_map) { - const CPubKey& pubkey = entry.second; - const CKeyID& id = entry.first; - CPubKey temp; - if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + for (const CKeyID& id : ordered_pubkeys) { + auto entry = pubkey_map.find(id); + if (entry == pubkey_map.end()) { + continue; } - } - if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated - if (!pwallet->AddWatchOnly(script, timestamp)) { + const CPubKey& pubkey = entry->second; + CPubKey temp; + if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + + // Add to keypool only works with pubkeys + if (add_keypool) { + pwallet->AddKeypoolPubkey(pubkey, internal); + } } - if (!internal) { - assert(IsValidDestination(dest)); - pwallet->SetAddressBook(dest, label, "receive"); + + for (const CScript& script : script_pub_keys) { + if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!pwallet->AddWatchOnly(script, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + CTxDestination dest; + ExtractDestination(script, dest); + if (!internal && IsValidDestination(dest)) { + pwallet->SetAddressBook(dest, label, "receive"); + } } result.pushKV("success", UniValue(true)); @@ -1148,16 +1350,21 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) if (mainRequest.fHelp || mainRequest.params.size() < 1 || mainRequest.params.size() > 2) throw std::runtime_error( RPCHelpMan{"importmulti", - "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), rescanning all addresses in one-shot-only (rescan can be disabled via options). Requires a new wallet backup.\n", + "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" + "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" + "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n" + "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" + "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n", { - {"requests", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "Data to be imported", + {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", { - {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"scriptPubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Type of scriptPubKey (string for script, json for address)", + {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"}, + {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor", /* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"} }, - {"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" + {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n" " or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" " key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" " \"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" @@ -1165,41 +1372,44 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) " creation time of all keys being imported by the importmulti call will be scanned.", /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"} }, - {"redeemscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, - {"witnessscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, - {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", + {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, + {"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, + {"pubkeys", RPCArg::Type::ARR, /* default */ "empty array", "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", { - {"pubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, + {"pubKey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, } }, - {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", + {"keys", RPCArg::Type::ARR, /* default */ "empty array", "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", { - {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, + {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, } }, - {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, - {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when not all private keys are provided."}, - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"}, + {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"}, + {"internal", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, + {"watchonly", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be considered watchonly."}, + {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with internal=false"}, + {"keypool", RPCArg::Type::BOOL, /* default */ "false", "Stating whether imported public keys should be added to the keypool for when users request new addresses. Only allowed when wallet private keys are disabled"}, }, }, }, "\"requests\""}, - {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"rescan", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Stating if should rescan the blockchain after all imports"}, + {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Stating if should rescan the blockchain after all imports"}, }, "\"options\""}, - }} - .ToString() + - "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" - "may report that the imported keys, addresses or scripts exists but related transactions are still missing.\n" - "\nExamples:\n" + - HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " - "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + - HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + - + }, + RPCResult{ "\nResponse is an array with the same size as the input that has the execution result :\n" - " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"); + " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n" + }, + RPCExamples{ + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " + "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + + HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + }, + }.ToString() + ); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); @@ -1232,15 +1442,16 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. - now = chainActive.Tip() ? chainActive.Tip()->GetMedianTimePast() : 0; + const Optional<int> tip_height = locked_chain->getHeight(); + now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0; for (const UniValue& data : requests.getValues()) { GetImportTimestamp(data, now); } const int64_t minimumTimestamp = 1; - if (fRescan && chainActive.Tip()) { - nLowestTimestamp = chainActive.Tip()->GetBlockTime(); + if (fRescan && tip_height) { + nLowestTimestamp = locked_chain->getBlockTime(*tip_height); } else { fRescan = false; } @@ -1267,7 +1478,11 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) } if (fRescan && fRunScan && requests.size()) { int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); - pwallet->ReacceptWalletTransactions(); + { + auto locked_chain = pwallet->chain().lock(); + LOCK(pwallet->cs_wallet); + pwallet->ReacceptWalletTransactions(*locked_chain); + } if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 38397a3940..0d804dcc10 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -7,29 +7,33 @@ #include <chain.h> #include <consensus/validation.h> #include <core_io.h> -#include <httpserver.h> #include <init.h> #include <interfaces/chain.h> #include <validation.h> #include <key_io.h> #include <net.h> +#include <node/transaction.h> #include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> -#include <rpc/mining.h> -#include <rpc/rawtransaction.h> +#include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <script/sign.h> #include <shutdown.h> #include <timedata.h> +#include <util/bip32.h> +#include <util/fees.h> #include <util/system.h> #include <util/moneystr.h> +#include <util/url.h> +#include <util/validation.h> #include <wallet/coincontrol.h> #include <wallet/feebumper.h> +#include <wallet/psbtwallet.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> @@ -102,7 +106,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo { entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockindex", wtx.nIndex); - entry.pushKV("blocktime", LookupBlockIndex(wtx.hashBlock)->GetBlockTime()); + int64_t block_time; + bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); + assert(found_block); + entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } @@ -118,8 +125,7 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo // Add opt-in RBF status std::string rbfStatus = "no"; if (confirms <= 0) { - LOCK(mempool.cs); - RBFTransactionState rbfState = IsRBFOptIn(*wtx.tx, mempool); + RBFTransactionState rbfState = chain.isRBFOptIn(*wtx.tx); if (rbfState == RBFTransactionState::UNKNOWN) rbfStatus = "unknown"; else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) @@ -155,23 +161,24 @@ static UniValue getnewaddress(const JSONRPCRequest& request) "If 'label' is specified, it is added to the address book \n" "so payments received with the address will be associated with 'label'.\n", { - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "The label name for the address to be linked to. If not provided, the default label \"\" is used. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."}, - {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + {"label", RPCArg::Type::STR, /* default */ "\"\"", "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."}, + {"address_type", RPCArg::Type::STR, /* default */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }, + RPCResult{ "\"address\" (string) The new bitcoin address\n" - "\nExamples:\n" - + HelpExampleCli("getnewaddress", "") + }, + RPCExamples{ + HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "") - ); - - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } + }, + }.ToString()); LOCK(pwallet->cs_wallet); + if (!pwallet->CanGetAddresses()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); + } + // Parse the label first so we don't generate a key if there's an error std::string label; if (!request.params[0].isNull()) @@ -216,22 +223,23 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n", { - {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + {"address_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }, + RPCResult{ "\"address\" (string) The address\n" - "\nExamples:\n" - + HelpExampleCli("getrawchangeaddress", "") + }, + RPCExamples{ + HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "") - ); - - if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); - } + }, + }.ToString()); LOCK(pwallet->cs_wallet); + if (!pwallet->CanGetAddresses(true)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); + } + if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } @@ -271,14 +279,15 @@ static UniValue setlabel(const JSONRPCRequest& request) RPCHelpMan{"setlabel", "\nSets the label associated with the given address.\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to be associated with a label."}, - {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label to assign to the address."}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to be associated with a label."}, + {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -301,7 +310,7 @@ static UniValue setlabel(const JSONRPCRequest& request) static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { - CAmount curBalance = pwallet->GetBalance(); + CAmount curBalance = pwallet->GetBalance().m_mine_trusted; // Check amount if (nValue <= 0) @@ -310,7 +319,7 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - if (pwallet->GetBroadcastTransactions() && !g_connman) { + if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } @@ -332,7 +341,7 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet throw JSONRPCError(RPC_WALLET_ERROR, strError); } CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservekey, g_connman.get(), state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservekey, state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } @@ -354,31 +363,32 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) "\nSend an amount to a given address." + HelpRequiringPassphrase(pwallet) + "\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to send to."}, - {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, - {"comment", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment used to store what the transaction is for.\n" + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to send to."}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, + {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment used to store what the transaction is for.\n" " This is not part of the transaction, just kept in your wallet."}, - {"comment_to", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment to store the name of the person or organization\n" + {"comment_to", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment to store the name of the person or organization\n" " to which you're sending the transaction. This is not part of the \n" " transaction, just kept in your wallet."}, - {"subtractfeefromamount", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "The fee will be deducted from the amount being sent.\n" + {"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n" " The recipient will receive less bitcoins than you enter in the amount field."}, - {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"txid\" (string) The transaction id.\n" - "\nExamples:\n" - + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + }, + RPCExamples{ + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -415,7 +425,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) } if (!request.params[6].isNull()) { - coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks()); } if (!request.params[7].isNull()) { @@ -446,9 +456,8 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" "in past transactions\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[\n" " [\n" " [\n" @@ -460,10 +469,12 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) " ]\n" " ,...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("listaddressgroupings", "") + }, + RPCExamples{ + HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -508,13 +519,13 @@ static UniValue signmessage(const JSONRPCRequest& request) "\nSign a message with the private key of an address" + HelpRequiringPassphrase(pwallet) + "\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to use for the private key."}, - {"message", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The message to create a signature of."}, - }} - .ToString() + - "\nResult:\n" + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the private key."}, + {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, + }, + RPCResult{ "\"signature\" (string) The signature of the message encoded in base 64\n" - "\nExamples:\n" + }, + RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" @@ -523,7 +534,8 @@ static UniValue signmessage(const JSONRPCRequest& request) + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -573,13 +585,13 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) RPCHelpMan{"getreceivedbyaddress", "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address for transactions."}, - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, - }} - .ToString() + - "\nResult:\n" + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for transactions."}, + {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only include transactions confirmed at least this many times."}, + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nThe amount from transactions with at least 1 confirmation\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + "\nThe amount including unconfirmed transactions, zero confirmations\n" @@ -588,13 +600,13 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -617,8 +629,9 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) CAmount nAmount = 0; for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + if (wtx.IsCoinBase() || !locked_chain->checkFinalTx(*wtx.tx)) { continue; + } for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) @@ -644,13 +657,13 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) RPCHelpMan{"getreceivedbylabel", "\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n", { - {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The selected label, may be the default label using \"\"."}, - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only include transactions confirmed at least this many times."}, - }} - .ToString() + - "\nResult:\n" + {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The selected label, may be the default label using \"\"."}, + {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only include transactions confirmed at least this many times."}, + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nAmount received by the default label with at least 1 confirmation\n" + HelpExampleCli("getreceivedbylabel", "\"\"") + "\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n" @@ -659,13 +672,13 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -682,8 +695,9 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) CAmount nAmount = 0; for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + if (wtx.IsCoinBase() || !locked_chain->checkFinalTx(*wtx.tx)) { continue; + } for (const CTxOut& txout : wtx.tx->vout) { @@ -715,21 +729,22 @@ static UniValue getbalance(const JSONRPCRequest& request) "The available balance is what the wallet considers currently spendable, and is\n" "thus affected by options which limit spendability such as -spendzeroconfchange.\n", { - {"dummy", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Remains for backward compatibility. Must be excluded or set to \"*\"."}, - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Only include transactions confirmed at least this many times."}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, - }} - .ToString() + - "\nResult:\n" + {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."}, + {"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see 'importaddress')"}, + }, + RPCResult{ "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nThe total amount in the wallet with 1 or more confirmations\n" + HelpExampleCli("getbalance", "") + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -748,12 +763,14 @@ static UniValue getbalance(const JSONRPCRequest& request) min_depth = request.params[1].get_int(); } - isminefilter filter = ISMINE_SPENDABLE; + bool include_watchonly = false; if (!request.params[2].isNull() && request.params[2].get_bool()) { - filter = filter | ISMINE_WATCH_ONLY; + include_watchonly = true; } - return ValueFromAmount(pwallet->GetBalance(filter, min_depth)); + const auto bal = pwallet->GetBalance(min_depth); + + return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); } static UniValue getunconfirmedbalance(const JSONRPCRequest &request) @@ -768,8 +785,11 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) if (request.fHelp || request.params.size() > 0) throw std::runtime_error( RPCHelpMan{"getunconfirmedbalance", - "Returns the server's total unconfirmed balance\n", {}} - .ToString()); + "Returns the server's total unconfirmed balance\n", + {}, + RPCResults{}, + RPCExamples{""}, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -778,7 +798,7 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ValueFromAmount(pwallet->GetUnconfirmedBalance()); + return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); } @@ -791,40 +811,38 @@ static UniValue sendmany(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) - throw std::runtime_error( - RPCHelpMan{"sendmany", + const RPCHelpMan help{"sendmany", "\nSend multiple times. Amounts are double-precision floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n", { - {"dummy", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Must be set to \"\" for backwards compatibility.", "\"\""}, - {"amounts", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "A json object with addresses and amounts", + {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", "\"\""}, + {"amounts", RPCArg::Type::OBJ, RPCArg::Optional::NO, "A json object with addresses and amounts", { - {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value"}, }, }, - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Only use the balance confirmed at least this many times."}, - {"comment", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A comment"}, - {"subtractfeefrom", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array with addresses.\n" + {"minconf", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "Ignored dummy value"}, + {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment"}, + {"subtractfeefrom", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array with addresses.\n" " The fee will be equally deducted from the amount of each selected address.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" " If no addresses are specified here, the sender pays the fee.", { - {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Subtract fee from this address"}, + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, - {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, + {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" - "\nExamples:\n" + }, + RPCExamples{ "\nSend two amounts to two different addresses:\n" + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + "\nSend two amounts to two different addresses setting the confirmation and comment:\n" @@ -833,7 +851,12 @@ static UniValue sendmany(const JSONRPCRequest& request) + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", {\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\":0.01,\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\":0.02}, 6, \"testing\"") - ); + }, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); + } // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -842,7 +865,7 @@ static UniValue sendmany(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - if (pwallet->GetBroadcastTransactions() && !g_connman) { + if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } @@ -850,9 +873,6 @@ static UniValue sendmany(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } UniValue sendTo = request.params[1].get_obj(); - int nMinDepth = 1; - if (!request.params[2].isNull()) - nMinDepth = request.params[2].get_int(); mapValue_t mapValue; if (!request.params[3].isNull() && !request.params[3].get_str().empty()) @@ -868,7 +888,7 @@ static UniValue sendmany(const JSONRPCRequest& request) } if (!request.params[6].isNull()) { - coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks()); } if (!request.params[7].isNull()) { @@ -880,7 +900,6 @@ static UniValue sendmany(const JSONRPCRequest& request) std::set<CTxDestination> destinations; std::vector<CRecipient> vecSend; - CAmount totalAmount = 0; std::vector<std::string> keys = sendTo.getKeys(); for (const std::string& name_ : keys) { CTxDestination dest = DecodeDestination(name_); @@ -897,7 +916,6 @@ static UniValue sendmany(const JSONRPCRequest& request) CAmount nAmount = AmountFromValue(sendTo[name_]); if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); - totalAmount += nAmount; bool fSubtractFeeFromAmount = false; for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) { @@ -912,11 +930,6 @@ static UniValue sendmany(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - // Check funds - if (totalAmount > pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth)) { - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Wallet has insufficient funds"); - } - // Shuffle recipient list std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); @@ -930,7 +943,7 @@ static UniValue sendmany(const JSONRPCRequest& request) if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, keyChange, g_connman.get(), state)) { + if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, keyChange, state)) { strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } @@ -956,27 +969,28 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) "See `importaddress` for watchonly p2sh address support.\n" "If 'label' is specified, assign address to that label.\n", { - {"nrequired", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The number of required signatures out of the n keys or addresses."}, - {"keys", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of bitcoin addresses or hex-encoded public keys", + {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or addresses."}, + {"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of bitcoin addresses or hex-encoded public keys", { - {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "bitcoin address or hex-encoded public key"}, + {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address or hex-encoded public key"}, }, }, - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "A label to assign the addresses to."}, - {"address_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - }} - .ToString() + - "\nResult:\n" + {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A label to assign the addresses to."}, + {"address_type", RPCArg::Type::STR, /* default */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + }, + RPCResult{ "{\n" " \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n" " \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") - ; + }, + }.ToString(); throw std::runtime_error(msg); } @@ -1031,8 +1045,6 @@ struct tallyitem static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { - LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. - // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) @@ -1063,8 +1075,9 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) + if (wtx.IsCoinBase() || !locked_chain.checkFinalTx(*wtx.tx)) { continue; + } int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) @@ -1189,13 +1202,12 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) RPCHelpMan{"listreceivedbyaddress", "\nList balances by receiving address.\n", { - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum number of confirmations before payments are included."}, - {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include addresses that haven't received any payments."}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, - {"address_filter", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If present, only return information on this address."}, - }} - .ToString() + - "\nResult:\n" + {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."}, + {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include addresses that haven't received any payments."}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present, only return information on this address."}, + }, + RPCResult{ "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" @@ -1210,13 +1222,14 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples:\n" - + HelpExampleCli("listreceivedbyaddress", "") + }, + RPCExamples{ + HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "6 true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1242,12 +1255,11 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) RPCHelpMan{"listreceivedbylabel", "\nList received transactions by label.\n", { - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum number of confirmations before payments are included."}, - {"include_empty", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include labels that haven't received any payments."}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, - }} - .ToString() + - "\nResult:\n" + {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are included."}, + {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include labels that haven't received any payments."}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, + }, + RPCResult{ "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" @@ -1257,12 +1269,13 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples:\n" - + HelpExampleCli("listreceivedbylabel", "") + }, + RPCExamples{ + HelpExampleCli("listreceivedbylabel", "") + HelpExampleCli("listreceivedbylabel", "6 true") + HelpExampleRpc("listreceivedbylabel", "6, true, true") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1292,7 +1305,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) +static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; @@ -1383,14 +1396,13 @@ UniValue listtransactions(const JSONRPCRequest& request) "\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n" "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n", { - {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If set, should be a valid label name to return only incoming transactions\n" + {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, should be a valid label name to return only incoming transactions\n" " with the specified label, or \"*\" to disable filtering and return all transactions."}, - {"count", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "10", "The number of transactions to return"}, - {"skip", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "The number of transactions to skip"}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, - }} - .ToString() + - "\nResult:\n" + {"count", RPCArg::Type::NUM, /* default */ "10", "The number of transactions to return"}, + {"skip", RPCArg::Type::NUM, /* default */ "0", "The number of transactions to skip"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + }, + RPCResult{ "[\n" " {\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" @@ -1422,15 +1434,16 @@ UniValue listtransactions(const JSONRPCRequest& request) " 'send' category of transactions.\n" " }\n" "]\n" - - "\nExamples:\n" + }, + RPCExamples{ "\nList the most recent 10 transactions in the systems\n" + HelpExampleCli("listtransactions", "") + "\nList transactions 100 to 120\n" + HelpExampleCli("listtransactions", "\"*\" 20 100") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1518,14 +1531,13 @@ static UniValue listsinceblock(const JSONRPCRequest& request) "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n", { - {"blockhash", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "If set, the block hash to list transactions since, otherwise list all transactions."}, - {"target_confirmations", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, - {"include_removed", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n" + {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise list all transactions."}, + {"target_confirmations", RPCArg::Type::NUM, /* default */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see 'importaddress')"}, + {"include_removed", RPCArg::Type::BOOL, /* default */ "true", "Show transactions that were removed due to a reorg in the \"removed\" array\n" " (not guaranteed to work on pruned nodes)"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"transactions\": [\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" @@ -1560,11 +1572,13 @@ static UniValue listsinceblock(const JSONRPCRequest& request) " ],\n" " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("listsinceblock", "") + }, + RPCExamples{ + HelpExampleCli("listsinceblock", "") + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1573,24 +1587,19 @@ static UniValue listsinceblock(const JSONRPCRequest& request) auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. - const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. + // The way the 'height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. + Optional<int> height = MakeOptional(false, int()); // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain. + Optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain. int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; + uint256 blockId; if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { - uint256 blockId(ParseHashV(request.params[0], "blockhash")); - - paltindex = pindex = LookupBlockIndex(blockId); - if (!pindex) { + blockId = ParseHashV(request.params[0], "blockhash"); + height = locked_chain->findFork(blockId, &altheight); + if (!height) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } - if (chainActive[pindex->nHeight] != pindex) { - // the block being asked for is a part of a deactivated chain; - // we don't want to depend on its perceived height in the block - // chain, we want to instead use the last common ancestor - pindex = chainActive.FindFork(pindex); - } } if (!request.params[1].isNull()) { @@ -1607,7 +1616,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request) bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); - int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; + const Optional<int> tip_height = locked_chain->getHeight(); + int depth = tip_height && height ? (1 + *tip_height - *height) : -1; UniValue transactions(UniValue::VARR); @@ -1622,9 +1632,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // when a reorg'd block is requested, we also list any relevant transactions // in the blocks of the chain that was detached UniValue removed(UniValue::VARR); - while (include_removed && paltindex && paltindex != pindex) { + while (include_removed && altheight && *altheight > *height) { CBlock block; - if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) { + if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } for (const CTransactionRef& tx : block.vtx) { @@ -1635,11 +1645,12 @@ static UniValue listsinceblock(const JSONRPCRequest& request) ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } - paltindex = paltindex->pprev; + blockId = block.hashPrevBlock; + --*altheight; } - CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; - uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); + int last_height = tip_height ? *tip_height + 1 - target_confirms : -1; + uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256(); UniValue ret(UniValue::VOBJ); ret.pushKV("transactions", transactions); @@ -1663,11 +1674,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) RPCHelpMan{"gettransaction", "\nGet detailed information about in-wallet transaction <txid>\n", { - {"txid", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction id"}, - {"include_watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Whether to include watch-only addresses in balance calculation and details[]"}, - }} - .ToString() + - "\nResult:\n" + {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, + {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses in balance calculation and details[]"}, + }, + RPCResult{ "{\n" " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" @@ -1702,12 +1712,13 @@ static UniValue gettransaction(const JSONRPCRequest& request) " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + }, + RPCExamples{ + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1745,7 +1756,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); - std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags()); + std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); return entry; @@ -1769,14 +1780,14 @@ static UniValue abandontransaction(const JSONRPCRequest& request) "It only works on transactions which are not included in a block and are not currently in the mempool.\n" "It has no effect on transactions which are already abandoned.\n", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, - }} - .ToString() + - "\nResult:\n" - "\nExamples:\n" - + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") - ); + }, + }.ToString()); } // Make sure the results are valid at least up to the most recent block @@ -1813,13 +1824,14 @@ static UniValue backupwallet(const JSONRPCRequest& request) RPCHelpMan{"backupwallet", "\nSafely copies current wallet file to destination, which can be a directory or a path with filename.\n", { - {"destination", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The destination directory or file"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("backupwallet", "\"backup.dat\"") + {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -1852,13 +1864,14 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) "\nFills the keypool."+ HelpRequiringPassphrase(pwallet) + "\n", { - {"newsize", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "100", "The new keypool size"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("keypoolrefill", "") + {"newsize", RPCArg::Type::NUM, /* default */ "100", "The new keypool size"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "") - ); + }, + }.ToString()); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -1899,23 +1912,24 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) throw std::runtime_error( RPCHelpMan{"walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" - "This is needed prior to performing transactions related to private keys such as sending bitcoins\n", - { - {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet passphrase"}, - {"timeout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, - }} - .ToString() + + "This is needed prior to performing transactions related to private keys such as sending bitcoins\n" "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" - "time that overrides the old one.\n" - "\nExamples:\n" + "time that overrides the old one.\n", + { + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"}, + {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at 100000000 (~3 years)."}, + }, + RPCResults{}, + RPCExamples{ "\nUnlock the wallet for 60 seconds\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -1960,7 +1974,7 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) // wallet before the following callback is called. If a valid shared pointer // is acquired in the callback then the wallet is still loaded. std::weak_ptr<CWallet> weak_wallet = wallet; - RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { + pwallet->chain().rpcRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { if (auto shared_wallet = weak_wallet.lock()) { LOCK(shared_wallet->cs_wallet); shared_wallet->Lock(); @@ -1986,14 +2000,15 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) RPCHelpMan{"walletpassphrasechange", "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n", { - {"oldpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The current passphrase"}, - {"newpassphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The new passphrase"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"}, + {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2040,9 +2055,9 @@ static UniValue walletlock(const JSONRPCRequest& request) "\nRemoves the wallet encryption key from memory, locking the wallet.\n" "After calling this method, you will need to call walletpassphrase again\n" "before being able to call any methods which require the wallet to be unlocked.\n", - {}} - .ToString() + - "\nExamples:\n" + {}, + RPCResults{}, + RPCExamples{ "\nSet the passphrase for 2 minutes to perform a transaction\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + "\nPerform a send (requires passphrase set)\n" @@ -2051,7 +2066,8 @@ static UniValue walletlock(const JSONRPCRequest& request) + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2086,10 +2102,10 @@ static UniValue encryptwallet(const JSONRPCRequest& request) "Use the walletpassphrase call for this, and then walletlock call.\n" "If the wallet is already encrypted, use the walletpassphrasechange call.\n", { - {"passphrase", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."}, - }} - .ToString() + - "\nExamples:\n" + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long."}, + }, + RPCResults{}, + RPCExamples{ "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" @@ -2100,7 +2116,8 @@ static UniValue encryptwallet(const JSONRPCRequest& request) + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2147,23 +2164,22 @@ static UniValue lockunspent(const JSONRPCRequest& request) "is always cleared (by virtue of process exit) when a node stops or fails.\n" "Also see the listunspent call\n", { - {"unlock", RPCArg::Type::BOOL, /* opt */ false, /* default_val */ "", "Whether to unlock (true) or lock (false) the specified transactions"}, - {"transactions", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of objects. Each object the txid (string) vout (numeric).", + {"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified transactions"}, + {"transactions", RPCArg::Type::ARR, /* default */ "empty array", "A json array of objects. Each object the txid (string) vout (numeric).", { - {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, - {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, }, }, }, }, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "true|false (boolean) Whether the command was successful or not\n" - - "\nExamples:\n" + }, + RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" @@ -2174,7 +2190,8 @@ static UniValue lockunspent(const JSONRPCRequest& request) + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2270,9 +2287,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) RPCHelpMan{"listlockunspent", "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for spending.\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[\n" " {\n" " \"txid\" : \"transactionid\", (string) The transaction id locked\n" @@ -2280,7 +2296,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - "\nExamples:\n" + }, + RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" @@ -2291,7 +2308,8 @@ static UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "") - ); + }, + }.ToString()); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -2326,15 +2344,16 @@ static UniValue settxfee(const JSONRPCRequest& request) RPCHelpMan{"settxfee", "\nSet the transaction fee per kB for this wallet. Overrides the global -paytxfee command line parameter.\n", { - {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The transaction fee in " + CURRENCY_UNIT + "/kB"}, - }} - .ToString() + - "\nResult\n" + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kB"}, + }, + RPCResult{ "true|false (boolean) Returns true if successful\n" - "\nExamples:\n" - + HelpExampleCli("settxfee", "0.00001") + }, + RPCExamples{ + HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001") - ); + }, + }.ToString()); } auto locked_chain = pwallet->chain().lock(); @@ -2344,8 +2363,8 @@ static UniValue settxfee(const JSONRPCRequest& request) CFeeRate tx_fee_rate(nAmount, 1000); if (tx_fee_rate == 0) { // automatic selection - } else if (tx_fee_rate < ::minRelayTxFee) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than min relay tx fee (%s)", ::minRelayTxFee.ToString())); + } else if (tx_fee_rate < pwallet->chain().relayMinFee()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than min relay tx fee (%s)", pwallet->chain().relayMinFee().ToString())); } else if (tx_fee_rate < pwallet->m_min_fee) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than wallet min fee (%s)", pwallet->m_min_fee.ToString())); } @@ -2366,9 +2385,9 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) throw std::runtime_error( RPCHelpMan{"getwalletinfo", - "Returns an object containing various wallet state info.\n", {}} - .ToString() + - "\nResult:\n" + "Returns an object containing various wallet state info.\n", + {}, + RPCResult{ "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n" @@ -2382,13 +2401,14 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" - " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getwalletinfo", "") + }, + RPCExamples{ + HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") - ); + }, + }.ToString()); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now @@ -2400,16 +2420,17 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + const auto bal = pwallet->GetBalance(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); - obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance())); - obj.pushKV("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())); - obj.pushKV("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())); + obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); + obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); + obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); 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; - if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { + if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); } if (pwallet->IsCrypted()) { @@ -2418,7 +2439,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); if (!seed_id.IsNull()) { obj.pushKV("hdseedid", seed_id.GetHex()); - obj.pushKV("hdmasterkeyid", seed_id.GetHex()); } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); return obj; @@ -2429,8 +2449,9 @@ static UniValue listwalletdir(const JSONRPCRequest& request) if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( RPCHelpMan{"listwalletdir", - "Returns a list of wallets in the wallet directory.\n", {}} - .ToString() + + "Returns a list of wallets in the wallet directory.\n", + {}, + RPCResult{ "{\n" " \"wallets\" : [ (json array of objects)\n" " {\n" @@ -2439,10 +2460,12 @@ static UniValue listwalletdir(const JSONRPCRequest& request) " ,...\n" " ]\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("listwalletdir", "") + }, + RPCExamples{ + HelpExampleCli("listwalletdir", "") + HelpExampleRpc("listwalletdir", "") - ); + }, + }.ToString()); } UniValue wallets(UniValue::VARR); @@ -2464,17 +2487,18 @@ static UniValue listwallets(const JSONRPCRequest& request) RPCHelpMan{"listwallets", "Returns a list of currently loaded wallets.\n" "For full information on the wallet, use \"getwalletinfo\"\n", - {}} - .ToString() + - "\nResult:\n" + {}, + RPCResult{ "[ (json array of strings)\n" " \"walletname\" (string) the wallet name\n" " ...\n" "]\n" - "\nExamples:\n" - + HelpExampleCli("listwallets", "") + }, + RPCExamples{ + HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "") - ); + }, + }.ToString()); UniValue obj(UniValue::VARR); @@ -2500,21 +2524,21 @@ static UniValue loadwallet(const JSONRPCRequest& request) "\nNote that all wallet command-line options used when starting bitcoind will be" "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, rescan, etc).\n", { - {"filename", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The wallet directory or .dat file."}, - }} - .ToString() + - "\nResult:\n" + {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, + }, + RPCResult{ "{\n" " \"name\" : <wallet_name>, (string) The wallet name if loaded successfully.\n" " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("loadwallet", "\"test.dat\"") + }, + RPCExamples{ + HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"") - ); + }, + }.ToString()); WalletLocation location(request.params[0].get_str()); - std::string error; if (!location.Exists()) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found."); @@ -2526,18 +2550,9 @@ static UniValue loadwallet(const JSONRPCRequest& request) } } - std::string warning; - if (!CWallet::Verify(*g_rpc_interfaces->chain, location, false, error, warning)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); - } - - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location); - if (!wallet) { - throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed."); - } - AddWallet(wallet); - - wallet->postInitProcess(); + std::string error, warning; + std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_interfaces->chain, location, error, warning); + if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error); UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); @@ -2548,31 +2563,37 @@ static UniValue loadwallet(const JSONRPCRequest& request) static UniValue createwallet(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) { throw std::runtime_error( RPCHelpMan{"createwallet", "\nCreates and loads a new wallet.\n", { - {"wallet_name", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The name for the new wallet. If this is a path, the wallet will be created at the path location."}, - {"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, - }} - .ToString() + - "\nResult:\n" + {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."}, + {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, + {"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."}, + }, + RPCResult{ "{\n" " \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n" " \"warning\" : <warning>, (string) Warning message if wallet was not loaded cleanly.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("createwallet", "\"testwallet\"") + }, + RPCExamples{ + HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"") - ); + }, + }.ToString()); } std::string error; std::string warning; - bool disable_privatekeys = false; - if (!request.params[1].isNull()) { - disable_privatekeys = request.params[1].get_bool(); + uint64_t flags = 0; + if (!request.params[1].isNull() && request.params[1].get_bool()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; + } + + if (!request.params[2].isNull() && request.params[2].get_bool()) { + flags |= WALLET_FLAG_BLANK_WALLET; } WalletLocation location(request.params[0].get_str()); @@ -2585,7 +2606,7 @@ static UniValue createwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); + std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, flags); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -2608,13 +2629,14 @@ static UniValue unloadwallet(const JSONRPCRequest& request) "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n" "Specifying the wallet name on a wallet endpoint is invalid.", { - {"wallet_name", RPCArg::Type::STR, /* opt */ true, /* default_val */ "the wallet name from the RPC request", "The name of the wallet to unload."}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("unloadwallet", "wallet_name") + {"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."}, + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name") - ); + }, + }.ToString()); } std::string wallet_name; @@ -2637,60 +2659,12 @@ static UniValue unloadwallet(const JSONRPCRequest& request) if (!RemoveWallet(wallet)) { throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); } - UnregisterValidationInterface(wallet.get()); - - // The wallet can be in use so it's not possible to explicitly unload here. - // Just notify the unload intent so that all shared pointers are released. - // The wallet will be destroyed once the last shared pointer is released. - wallet->NotifyUnload(); - // There's no point in waiting for the wallet to unload. - // At this point this method should never fail. The unloading could only - // fail due to an unexpected error which would cause a process termination. + UnloadWallet(std::move(wallet)); return NullUniValue; } -static UniValue resendwallettransactions(const JSONRPCRequest& request) -{ - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() != 0) - throw std::runtime_error( - RPCHelpMan{"resendwallettransactions", - "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" - "Intended only for testing; the wallet code periodically re-broadcasts\n", - {}} - .ToString() + - "automatically.\n" - "Returns an RPC error if -walletbroadcast is set to false.\n" - "Returns array of transaction ids that were re-broadcast.\n" - ); - - if (!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); - - auto locked_chain = pwallet->chain().lock(); - LOCK(pwallet->cs_wallet); - - if (!pwallet->GetBroadcastTransactions()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); - } - - std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(*locked_chain, GetTime(), g_connman.get()); - UniValue result(UniValue::VARR); - for (const uint256& txid : txids) - { - result.push_back(txid.ToString()); - } - return result; -} - static UniValue listunspent(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -2707,26 +2681,25 @@ static UniValue listunspent(const JSONRPCRequest& request) "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified addresses.\n", { - {"minconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1", "The minimum confirmations to filter"}, - {"maxconf", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "9999999", "The maximum confirmations to filter"}, - {"addresses", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of bitcoin addresses to filter", + {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum confirmations to filter"}, + {"maxconf", RPCArg::Type::NUM, /* default */ "9999999", "The maximum confirmations to filter"}, + {"addresses", RPCArg::Type::ARR, /* default */ "empty array", "A json array of bitcoin addresses to filter", { - {"address", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "bitcoin address"}, + {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address"}, }, }, - {"include_unsafe", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Include outputs that are not safe to spend\n" + {"include_unsafe", RPCArg::Type::BOOL, /* default */ "true", "Include outputs that are not safe to spend\n" " See description of \"safe\" attribute below."}, - {"query_options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "JSON with query options", + {"query_options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "JSON with query options", { - {"minimumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "0", "Minimum value of each UTXO in " + CURRENCY_UNIT + ""}, - {"maximumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "unlimited", "Maximum value of each UTXO in " + CURRENCY_UNIT + ""}, - {"maximumCount", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "unlimited", "Maximum number of UTXOs"}, - {"minimumSumAmount", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "unlimited", "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, + {"minimumAmount", RPCArg::Type::AMOUNT, /* default */ "0", "Minimum value of each UTXO in " + CURRENCY_UNIT + ""}, + {"maximumAmount", RPCArg::Type::AMOUNT, /* default */ "unlimited", "Maximum value of each UTXO in " + CURRENCY_UNIT + ""}, + {"maximumCount", RPCArg::Type::NUM, /* default */ "unlimited", "Maximum number of UTXOs"}, + {"minimumSumAmount", RPCArg::Type::AMOUNT, /* default */ "unlimited", "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, }, "query_options"}, - }} - .ToString() + - "\nResult\n" + }, + RPCResult{ "[ (array of json object)\n" " {\n" " \"txid\" : \"txid\", (string) the transaction id \n" @@ -2736,7 +2709,8 @@ static UniValue listunspent(const JSONRPCRequest& request) " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n" " \"confirmations\" : n, (numeric) The number of confirmations\n" - " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" + " \"redeemScript\" : \"script\" (string) The redeemScript if scriptPubKey is P2SH\n" + " \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n" " \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n" " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" " \"desc\" : xxx, (string, only when solvable) A descriptor for spending this output\n" @@ -2746,14 +2720,15 @@ static UniValue listunspent(const JSONRPCRequest& request) " }\n" " ,...\n" "]\n" - - "\nExamples\n" - + HelpExampleCli("listunspent", "") + }, + RPCExamples{ + HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") - ); + }, + }.ToString()); int nMinDepth = 1; if (!request.params[0].isNull()) { @@ -2849,6 +2824,28 @@ static UniValue listunspent(const JSONRPCRequest& request) CScript redeemScript; if (pwallet->GetCScript(hash, redeemScript)) { entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + // Now check if the redeemScript is actually a P2WSH script + CTxDestination witness_destination; + if (redeemScript.IsPayToWitnessScriptHash()) { + bool extracted = ExtractDestination(redeemScript, witness_destination); + assert(extracted); + // Also return the witness script + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (pwallet->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } + } + } + } else if (scriptPubKey.IsPayToWitnessScriptHash()) { + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (pwallet->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); } } } @@ -2948,7 +2945,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f if (options.exists("feeRate")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate"); } - coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]); + coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"], pwallet->chain().estimateMaxBlocks()); } if (options.exists("estimate_mode")) { if (options.exists("feeRate")) { @@ -3001,59 +2998,61 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) "This will not modify existing inputs, and will add at most one change output to the outputs.\n" "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" - "The inputs added will not be signed, use signrawtransaction for that.\n" + "The inputs added will not be signed, use signrawtransactionwithkey\n" + " or signrawtransactionwithwallet for that.\n" "Note that all existing inputs must have their previous output transaction be in the wallet.\n" "Note that all inputs selected must be of standard form and P2SH scripts must be\n" "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n", { - {"hexstring", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The hex string of the raw transaction"}, - {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}", + {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, + {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}", { - {"changeAddress", RPCArg::Type::STR, /* opt */ true, /* default_val */ "pool address", "The bitcoin address to receive the change"}, - {"changePosition", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "random", "The index of the change output"}, - {"change_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - {"includeWatching", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also select inputs which are watch only"}, - {"lockUnspents", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Lock selected unspent outputs"}, - {"feeRate", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, - {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of integers.\n" + {"changeAddress", RPCArg::Type::STR, /* default */ "pool address", "The bitcoin address to receive the change"}, + {"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, + {"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, + {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, + {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" " If no outputs are specified here, the sender pays the fee.", { - {"vout_index", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "", "The zero-based output index, before a change output is added."}, + {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "fallback to wallet's default", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, - {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" + {"conf_target", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, }, "options"}, - {"iswitness", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" + {"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n" " If iswitness is not present, heuristic tests will be used in decoding"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the added change output, or -1\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + "\nAdd sufficient unsigned inputs to meet the output value\n" + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + "\nSign the transaction\n" - + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + + HelpExampleCli("signrawtransactionwithwallet", "\"fundedtransactionhex\"") + "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); @@ -3070,7 +3069,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); - result.pushKV("hex", EncodeHexTx(tx)); + result.pushKV("hex", EncodeHexTx(CTransaction(tx))); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); @@ -3094,30 +3093,30 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) "this transaction depends on but may not yet be in the block chain." + HelpRequiringPassphrase(pwallet) + "\n", { - {"hexstring", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction hex string"}, - {"prevtxs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "null", "A json array of previous dependent transaction outputs", + {"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"}, + {"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of previous dependent transaction outputs", { - {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, - {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, - {"scriptPubKey", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "script key"}, - {"redeemScript", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "omitted", "(required for P2SH or P2WSH)"}, - {"amount", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "The amount spent"}, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, + {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, + {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"}, + {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"}, + {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"}, }, }, }, }, - {"sighashtype", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ALL", "The signature hash type. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, /* default */ "ALL", "The signature hash type. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" @@ -3132,11 +3131,12 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) " ,...\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + }, + RPCExamples{ + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); @@ -3167,9 +3167,9 @@ static UniValue bumpfee(const JSONRPCRequest& request) RPCHelpMan{"bumpfee", "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" - "The command will pay the additional fee by decreasing (or perhaps removing) its change output.\n" - "If the change output is not big enough to cover the increased fee, the command will currently fail\n" - "instead of adding new inputs to compensate. (A future implementation could improve this.)\n" + "The command will pay the additional fee by reducing change outputs or adding inputs when necessary. It may add a new change output if one does not already exist.\n" + "If `totalFee` is given, adding inputs is not supported, so there must be a single change output that is big enough or it will fail.\n" + "All inputs in the original transaction will be included in the replacement transaction.\n" "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" "By default, the new fee will be calculated automatically using estimatesmartfee.\n" "The user can specify a confirmation target for estimatesmartfee.\n" @@ -3177,39 +3177,41 @@ static UniValue bumpfee(const JSONRPCRequest& request) "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" "returned by getnetworkinfo) to enter the node's mempool.\n", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The txid to be bumped"}, - {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"}, + {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"confTarget", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to wallet's default", "Confirmation target (in blocks)"}, - {"totalFee", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n" + {"confTarget", RPCArg::Type::NUM, /* default */ "fallback to wallet's default", "Confirmation target (in blocks)"}, + {"totalFee", RPCArg::Type::NUM, /* default */ "fallback to 'confTarget'", "Total fee (NOT feerate) to pay, in satoshis.\n" " In rare cases, the actual fee paid might be slightly higher than the specified\n" " totalFee if the tx change output has to be removed because it is too close to\n" " the dust threshold."}, - {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether the new transaction should still be\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n" " marked bip-125 replaceable. If true, the sequence numbers in the transaction will\n" " be left unchanged from the original. If false, any input sequence numbers in the\n" " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n" " so the new transaction will not be explicitly bip-125 replaceable (though it may\n" " still be replaceable in practice, for example if it has unconfirmed ancestors which\n" " are replaceable)."}, - {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" + {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, }, "options"}, - }} - .ToString() + - "\nResult:\n" + }, + RPCResult{ "{\n" " \"txid\": \"value\", (string) The id of the new transaction\n" " \"origfee\": n, (numeric) Fee of the replaced transaction\n" " \"fee\": n, (numeric) Fee of the new transaction\n" " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nBump the fee, get the new transaction\'s txid\n" + - HelpExampleCli("bumpfee", "<txid>")); + HelpExampleCli("bumpfee", "<txid>") + }, + }.ToString()); } RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); @@ -3233,7 +3235,7 @@ static UniValue bumpfee(const JSONRPCRequest& request) if (options.exists("confTarget") && options.exists("totalFee")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction."); } else if (options.exists("confTarget")) { // TODO: alias this to conf_target - coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]); + coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"], pwallet->chain().estimateMaxBlocks()); } else if (options.exists("totalFee")) { totalFee = options["totalFee"].get_int64(); if (totalFee <= 0) { @@ -3264,7 +3266,14 @@ static UniValue bumpfee(const JSONRPCRequest& request) CAmount old_fee; CAmount new_fee; CMutableTransaction mtx; - feebumper::Result res = feebumper::CreateTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx); + feebumper::Result res; + if (totalFee > 0) { + // Targeting total fee bump. Requires a change output of sufficient size. + res = feebumper::CreateTotalBumpTransaction(pwallet, hash, coin_control, totalFee, errors, old_fee, new_fee, mtx); + } else { + // Targeting feerate bump. + res = feebumper::CreateRateBumpTransaction(pwallet, hash, coin_control, errors, old_fee, new_fee, mtx); + } if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: @@ -3307,61 +3316,6 @@ static UniValue bumpfee(const JSONRPCRequest& request) return result; } -UniValue generate(const JSONRPCRequest& request) -{ - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw std::runtime_error( - RPCHelpMan{"generate", - "\nMine up to nblocks blocks immediately (before the RPC call returns) to an address in the wallet.\n", - { - {"nblocks", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "How many blocks are generated immediately."}, - {"maxtries", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "1000000", "How many iterations to try."}, - }} - .ToString() + - "\nResult:\n" - "[ blockhashes ] (array) hashes of blocks generated\n" - "\nExamples:\n" - "\nGenerate 11 blocks\n" - + HelpExampleCli("generate", "11") - ); - } - - if (!IsDeprecatedRPCEnabled("generate")) { - throw JSONRPCError(RPC_METHOD_DEPRECATED, "The wallet generate rpc method is deprecated and will be fully removed in v0.19. " - "To use generate in v0.18, restart bitcoind with -deprecatedrpc=generate.\n" - "Clients should transition to using the node rpc method generatetoaddress\n"); - } - - int num_generate = request.params[0].get_int(); - uint64_t max_tries = 1000000; - if (!request.params[1].isNull()) { - max_tries = request.params[1].get_int(); - } - - std::shared_ptr<CReserveScript> coinbase_script; - pwallet->GetScriptForMining(coinbase_script); - - // If the keypool is exhausted, no script is returned at all. Catch this. - if (!coinbase_script) { - throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); - } - - //throw an error if no script was provided - if (coinbase_script->reserveScript.empty()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available"); - } - - return generateBlocks(coinbase_script, num_generate, max_tries, true); -} - UniValue rescanblockchain(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -3376,19 +3330,20 @@ UniValue rescanblockchain(const JSONRPCRequest& request) RPCHelpMan{"rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n", { - {"start_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "block height where the rescan should start"}, - {"stop_height", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "tip height", "the last block height that should be scanned"}, - }} - .ToString() + - "\nResult:\n" + {"start_height", RPCArg::Type::NUM, /* default */ "0", "block height where the rescan should start"}, + {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."}, + }, + RPCResult{ "{\n" - " \"start_height\" (numeric) The block height where the rescan has started. If omitted, rescan started from the genesis block.\n" - " \"stop_height\" (numeric) The height of the last rescanned block. If omitted, rescan stopped at the chain tip.\n" + " \"start_height\" (numeric) The block height where the rescan started (the requested height or 0)\n" + " \"stop_height\" (numeric) The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background.\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("rescanblockchain", "100000 120000") + }, + RPCExamples{ + HelpExampleCli("rescanblockchain", "100000 120000") + HelpExampleRpc("rescanblockchain", "100000, 120000") - ); + }, + }.ToString()); } WalletRescanReserver reserver(pwallet); @@ -3396,50 +3351,53 @@ UniValue rescanblockchain(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - CBlockIndex *pindexStart = nullptr; - CBlockIndex *pindexStop = nullptr; - CBlockIndex *pChainTip = nullptr; + int start_height = 0; + uint256 start_block, stop_block; { auto locked_chain = pwallet->chain().lock(); - pindexStart = chainActive.Genesis(); - pChainTip = chainActive.Tip(); + Optional<int> tip_height = locked_chain->getHeight(); if (!request.params[0].isNull()) { - pindexStart = chainActive[request.params[0].get_int()]; - if (!pindexStart) { + start_height = request.params[0].get_int(); + if (start_height < 0 || !tip_height || start_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } + Optional<int> stop_height; if (!request.params[1].isNull()) { - pindexStop = chainActive[request.params[1].get_int()]; - if (!pindexStop) { + stop_height = request.params[1].get_int(); + if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } - else if (pindexStop->nHeight < pindexStart->nHeight) { + else if (*stop_height < start_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height"); } } - } - // We can't rescan beyond non-pruned blocks, stop and throw an error - if (fPruneMode) { - auto locked_chain = pwallet->chain().lock(); - CBlockIndex *block = pindexStop ? pindexStop : pChainTip; - while (block && block->nHeight >= pindexStart->nHeight) { - if (!(block->nStatus & BLOCK_HAVE_DATA)) { - throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); + // We can't rescan beyond non-pruned blocks, stop and throw an error + if (locked_chain->findPruned(start_height, stop_height)) { + throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height."); + } + + if (tip_height) { + start_block = locked_chain->getBlockHash(start_height); + // If called with a stop_height, set the stop_height here to + // trigger a rescan to that height. + // If called without a stop height, leave stop_height as null here + // so rescan continues to the tip (even if the tip advances during + // rescan). + if (stop_height) { + stop_block = locked_chain->getBlockHash(*stop_height); } - block = block->pprev; } } - const CBlockIndex *failed_block, *stopBlock; CWallet::ScanResult result = - pwallet->ScanForWalletTransactions(pindexStart, pindexStop, reserver, failed_block, stopBlock, true); - switch (result) { + pwallet->ScanForWalletTransactions(start_block, stop_block, reserver, true /* fUpdate */); + switch (result.status) { case CWallet::ScanResult::SUCCESS: - break; // stopBlock set by ScanForWalletTransactions + break; case CWallet::ScanResult::FAILURE: throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); case CWallet::ScanResult::USER_ABORT: @@ -3447,8 +3405,8 @@ UniValue rescanblockchain(const JSONRPCRequest& request) // no default case, so the compiler can warn about missing cases } UniValue response(UniValue::VOBJ); - response.pushKV("start_height", pindexStart->nHeight); - response.pushKV("stop_height", stopBlock->nHeight); + response.pushKV("start_height", start_height); + response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue()); return response; } @@ -3457,7 +3415,7 @@ class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> public: CWallet * const pwallet; - void ProcessSubScript(const CScript& subscript, UniValue& obj, bool include_addresses = false) const + void ProcessSubScript(const CScript& subscript, UniValue& obj) const { // Always present: script type and redeemscript std::vector<std::vector<unsigned char>> solutions_data; @@ -3466,7 +3424,6 @@ public: obj.pushKV("hex", HexStr(subscript.begin(), subscript.end())); CTxDestination embedded; - UniValue a(UniValue::VARR); if (ExtractDestination(subscript, embedded)) { // Only when the script corresponds to an address. UniValue subobj(UniValue::VOBJ); @@ -3479,7 +3436,6 @@ public: // Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works. if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]); obj.pushKV("embedded", std::move(subobj)); - if (include_addresses) a.push_back(EncodeDestination(embedded)); } else if (which_type == TX_MULTISIG) { // Also report some information on multisig scripts (which do not have a corresponding address). // TODO: abstract out the common functionality between this logic and ExtractDestinations. @@ -3487,17 +3443,10 @@ public: UniValue pubkeys(UniValue::VARR); for (size_t i = 1; i < solutions_data.size() - 1; ++i) { CPubKey key(solutions_data[i].begin(), solutions_data[i].end()); - if (include_addresses) a.push_back(EncodeDestination(key.GetID())); pubkeys.push_back(HexStr(key.begin(), key.end())); } obj.pushKV("pubkeys", std::move(pubkeys)); } - - // The "addresses" field is confusing because it refers to public keys using their P2PKH address. - // For that reason, only add the 'addresses' field when needed for backward compatibility. New applications - // can use the 'embedded'->'address' field for P2SH or P2WSH wrapped addresses, and 'pubkeys' for - // inspecting multisig participants. - if (include_addresses) obj.pushKV("addresses", std::move(a)); } explicit DescribeWalletAddressVisitor(CWallet* _pwallet) : pwallet(_pwallet) {} @@ -3520,7 +3469,7 @@ public: UniValue obj(UniValue::VOBJ); CScript subscript; if (pwallet && pwallet->GetCScript(scriptID, subscript)) { - ProcessSubScript(subscript, obj, IsDeprecatedRPCEnabled("validateaddress")); + ProcessSubScript(subscript, obj); } return obj; } @@ -3586,15 +3535,13 @@ UniValue getaddressinfo(const JSONRPCRequest& request) "\nReturn information about the given bitcoin address. Some information requires the address\n" "to be in the wallet.\n", { - {"address", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The bitcoin address to get the information of."}, - }} - .ToString() + - "\nResult:\n" + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."}, + }, + RPCResult{ "{\n" " \"address\" : \"address\", (string) The bitcoin address validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"solvable\" : true|false, (boolean) If the address is solvable by the wallet\n" " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n" " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n" @@ -3613,12 +3560,12 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n" - " \"iscompressed\" : true|false, (boolean) If the address is compressed\n" + " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n" " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n" " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n" - " \"hdmasterkeyid\" : \"<hash160>\" (string, optional) alias for hdseedid maintained for backwards compatibility. Will be removed in V0.18.\n" + " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n" " \"labels\" (object) Array of labels associated with the address.\n" " [\n" " { (json object of label data)\n" @@ -3627,10 +3574,12 @@ UniValue getaddressinfo(const JSONRPCRequest& request) " },...\n" " ]\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + }, + RPCExamples{ + HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - ); + }, + }.ToString()); } LOCK(pwallet->cs_wallet); @@ -3657,7 +3606,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); } ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); - ret.pushKV("solvable", IsSolvable(*pwallet, scriptPubKey)); UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); if (pwallet->mapAddressBook.count(dest)) { @@ -3680,10 +3628,10 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } if (meta) { ret.pushKV("timestamp", meta->nCreateTime); - if (!meta->hdKeypath.empty()) { - ret.pushKV("hdkeypath", meta->hdKeypath); + if (meta->has_key_origin) { + ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path)); ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); - ret.pushKV("hdmasterkeyid", meta->hd_seed_id.GetHex()); + ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4)); } } @@ -3714,19 +3662,20 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) RPCHelpMan{"getaddressesbylabel", "\nReturns the list of addresses assigned the specified label.\n", { - {"label", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The label."}, - }} - .ToString() + - "\nResult:\n" + {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."}, + }, + RPCResult{ "{ (json object with addresses as keys)\n" " \"address\": { (json object with information about address)\n" " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n" " },...\n" "}\n" - "\nExamples:\n" - + HelpExampleCli("getaddressesbylabel", "\"tabby\"") + }, + RPCExamples{ + HelpExampleCli("getaddressesbylabel", "\"tabby\"") + HelpExampleRpc("getaddressesbylabel", "\"tabby\"") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -3761,15 +3710,15 @@ static UniValue listlabels(const JSONRPCRequest& request) RPCHelpMan{"listlabels", "\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n", { - {"purpose", RPCArg::Type::STR, /* opt */ true, /* default_val */ "null", "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."}, - }} - .ToString() + - "\nResult:\n" + {"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."}, + }, + RPCResult{ "[ (json array of string)\n" " \"label\", (string) Label name\n" " ...\n" "]\n" - "\nExamples:\n" + }, + RPCExamples{ "\nList all labels\n" + HelpExampleCli("listlabels", "") + "\nList labels that have receiving addresses\n" @@ -3778,7 +3727,8 @@ static UniValue listlabels(const JSONRPCRequest& request) + HelpExampleCli("listlabels", "send") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive") - ); + }, + }.ToString()); LOCK(pwallet->cs_wallet); @@ -3820,31 +3770,36 @@ UniValue sethdseed(const JSONRPCRequest& request) "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HelpRequiringPassphrase(pwallet) + "\n", { - {"newkeypool", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n" + {"newkeypool", RPCArg::Type::BOOL, /* default */ "true", "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n" " If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n" " If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n" " keypool will be used until it has been depleted."}, - {"seed", RPCArg::Type::STR, /* opt */ true, /* default_val */ "random seed", "The WIF private key to use as the new HD seed.\n" + {"seed", RPCArg::Type::STR, /* default */ "random seed", "The WIF private key to use as the new HD seed.\n" " The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"}, - }} - .ToString() + - "\nExamples:\n" - + HelpExampleCli("sethdseed", "") + }, + RPCResults{}, + RPCExamples{ + HelpExampleCli("sethdseed", "") + HelpExampleCli("sethdseed", "false") + HelpExampleCli("sethdseed", "true \"wifkey\"") + HelpExampleRpc("sethdseed", "true, \"wifkey\"") - ); + }, + }.ToString()); } - if (IsInitialBlockDownload()) { + if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled"); + } + auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // Do not do anything to non-HD wallets - if (!pwallet->IsHDEnabled()) { + if (!pwallet->CanSupportFeature(FEATURE_HD)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with -upgradewallet in order to upgrade a non-HD wallet to HD"); } @@ -3877,73 +3832,6 @@ UniValue sethdseed(const JSONRPCRequest& request) return NullUniValue; } -void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, KeyOriginInfo>& hd_keypaths) -{ - CPubKey vchPubKey; - if (!pwallet->GetPubKey(keyID, vchPubKey)) { - return; - } - KeyOriginInfo info; - if (!pwallet->GetKeyOrigin(keyID, info)) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken"); - } - hd_keypaths.emplace(vchPubKey, std::move(info)); -} - -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) -{ - LOCK(pwallet->cs_wallet); - // Get all of the previous transactions - bool complete = true; - for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - const CTxIn& txin = psbtx.tx->vin[i]; - PSBTInput& input = psbtx.inputs.at(i); - - if (PSBTInputSigned(input)) { - continue; - } - - // Verify input looks sane. This will check that we have at most one uxto, witness or non-witness. - if (!input.IsSane()) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane."); - } - - // If we have no utxo, grab it from the wallet. - if (!input.non_witness_utxo && input.witness_utxo.IsNull()) { - const uint256& txhash = txin.prevout.hash; - const auto it = pwallet->mapWallet.find(txhash); - if (it != pwallet->mapWallet.end()) { - const CWalletTx& wtx = it->second; - // We only need the non_witness_utxo, which is a superset of the witness_utxo. - // The signing code will switch to the smaller witness_utxo if this is ok. - input.non_witness_utxo = wtx.tx; - } - } - - // Get the Sighash type - if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); - } - - complete &= SignPSBTInput(HidingSigningProvider(pwallet, !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) { - const CTxOut& out = psbtx.tx->vout.at(i); - PSBTOutput& psbt_out = psbtx.outputs.at(i); - - // Fill a SignatureData with output info - SignatureData sigdata; - psbt_out.FillSignatureData(sigdata); - - MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); - ProduceSignature(HidingSigningProvider(pwallet, true, !bip32derivs), creator, out.scriptPubKey, sigdata); - psbt_out.FromSignatureData(sigdata); - } - return complete; -} - UniValue walletprocesspsbt(const JSONRPCRequest& request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); @@ -3960,35 +3848,35 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) "that we can sign for." + HelpRequiringPassphrase(pwallet) + "\n", { - {"psbt", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The transaction base64 string"}, - {"sign", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "true", "Also sign the transaction when updating"}, - {"sighashtype", RPCArg::Type::STR, /* opt */ true, /* default_val */ "ALL", "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, + {"sign", RPCArg::Type::BOOL, /* default */ "true", "Also sign the transaction when updating"}, + {"sighashtype", RPCArg::Type::STR, /* default */ "ALL", "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" " \"ALL|ANYONECANPAY\"\n" " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, - {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, - }} - .ToString() + - "\nResult:\n" + {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + }, + RPCResult{ "{\n" " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" " ]\n" "}\n" - - "\nExamples:\n" - + HelpExampleCli("walletprocesspsbt", "\"psbt\"") - ); + }, + RPCExamples{ + HelpExampleCli("walletprocesspsbt", "\"psbt\"") + }, + }.ToString()); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; - if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } @@ -3998,7 +3886,11 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request) // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); - bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs); + bool complete = true; + const TransactionError err = FillPSBT(pwallet, psbtx, complete, nHashType, sign, bip32derivs); + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err); + } UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -4024,74 +3916,74 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" "Implements the Creator and Updater roles.\n", { - {"inputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A json array of json objects", + {"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of json objects", { - {"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id"}, - {"vout", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The output number"}, - {"sequence", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The sequence number"}, + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, + {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"}, }, }, }, }, - {"outputs", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" "That is, each address can only appear once and there can only be one 'data' object.\n" "For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" " accepted as second parameter.", { - {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"address", RPCArg::Type::AMOUNT, /* opt */ false, /* default_val */ "", "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, }, }, - {"", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "", + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { - {"data", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, }, }, }, }, - {"locktime", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Raw locktime. Non-0 value also locktime-activates inputs\n" - " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible."}, - {"options", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "null", "", + {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, + {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { - {"changeAddress", RPCArg::Type::STR_HEX, /* opt */ true, /* default_val */ "pool address", "The bitcoin address to receive the change"}, - {"changePosition", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "random", "The index of the change output"}, - {"change_type", RPCArg::Type::STR, /* opt */ true, /* default_val */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, - {"includeWatching", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Also select inputs which are watch only"}, - {"lockUnspents", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Lock selected unspent outputs"}, - {"feeRate", RPCArg::Type::AMOUNT, /* opt */ true, /* default_val */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, - {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "A json array of integers.\n" + {"changeAddress", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"}, + {"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, + {"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, + {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, + {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, + {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, + {"subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" " The fee will be equally deducted from the amount of each specified output.\n" " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" " If no outputs are specified here, the sender pays the fee.", { - {"vout_index", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "", "The zero-based output index, before a change output is added."}, + {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, - {"replaceable", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Marks this transaction as BIP125 replaceable.\n" + {"replaceable", RPCArg::Type::BOOL, /* default */ "false", "Marks this transaction as BIP125 replaceable.\n" " Allows this transaction to be replaced by a transaction with higher fees"}, - {"conf_target", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"}, - {"estimate_mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "UNSET", "The fee estimate mode, must be one of:\n" + {"conf_target", RPCArg::Type::NUM, /* default */ "Fallback to wallet's confirmation target", "Confirmation target (in blocks)"}, + {"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n" " \"UNSET\"\n" " \"ECONOMICAL\"\n" " \"CONSERVATIVE\""}, }, "options"}, - {"bip32derivs", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, - }} - .ToString() + - "\nResult:\n" + {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"}, + }, + RPCResult{ "{\n" " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the added change output, or -1\n" "}\n" - "\nExamples:\n" + }, + RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - ); + }, + }.ToString()); RPCTypeCheck(request.params, { UniValue::VARR, @@ -4112,7 +4004,11 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); - FillPSBT(pwallet, psbtx, 1, false, bip32derivs); + bool complete = true; + const TransactionError err = FillPSBT(pwallet, psbtx, complete, 1, false, bip32derivs); + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err); + } // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -4140,15 +4036,13 @@ UniValue importmulti(const JSONRPCRequest& request); static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- - { "generating", "generate", &generate, {"nblocks","maxtries"} }, - { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, - { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} }, + { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, @@ -4199,8 +4093,8 @@ static const CRPCCommand commands[] = }; // clang-format on -void RegisterWalletRPCCommands(CRPCTable &t) +void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique_ptr<interfaces::Handler>>& handlers) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) - t.appendCommand(commands[vcidx].name, &commands[vcidx]); + handlers.emplace_back(chain.handleRpc(commands[vcidx])); } diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index abd7750874..7cf607ccc7 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -5,7 +5,9 @@ #ifndef BITCOIN_WALLET_RPCWALLET_H #define BITCOIN_WALLET_RPCWALLET_H +#include <memory> #include <string> +#include <vector> class CRPCTable; class CWallet; @@ -14,7 +16,12 @@ class UniValue; struct PartiallySignedTransaction; class CTransaction; -void RegisterWalletRPCCommands(CRPCTable &t); +namespace interfaces { +class Chain; +class Handler; +} + +void RegisterWalletRPCCommands(interfaces::Chain& chain, std::vector<std::unique_ptr<interfaces::Handler>>& handlers); /** * Figures out what wallet, if any, to use for a JSONRPCRequest. @@ -30,5 +37,4 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); -bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false); #endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 5c65acf601..b7ca746f8b 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -8,7 +8,7 @@ #include <amount.h> #include <primitives/transaction.h> #include <random.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> @@ -29,7 +29,7 @@ typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; static auto testChain = interfaces::MakeChain(); -static CWallet testWallet(*testChain, WalletLocation(), WalletDatabase::CreateDummy()); +static CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); static CAmount balance = 0; CoinEligibilityFilter filter_standard(1, 6, 0); @@ -135,7 +135,6 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) ///////////////////////// // Known Outcome tests // ///////////////////////// - BOOST_TEST_MESSAGE("Testing known outcomes"); // Empty utxo pool BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 0.5 * CENT, selection, value_ret, not_input_fees)); diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp new file mode 100644 index 0000000000..d9b07af329 --- /dev/null +++ b/src/wallet/test/db_tests.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 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 <memory> + +#include <boost/test/unit_test.hpp> + +#include <fs.h> +#include <test/setup_common.h> +#include <wallet/db.h> + + +BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(getwalletenv_file) +{ + std::string test_name = "test_name.dat"; + fs::path datadir = SetDataDir("tempdir"); + fs::path file_path = datadir / test_name; + std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR); + f.close(); + + std::string filename; + std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename); + BOOST_CHECK(filename == test_name); + BOOST_CHECK(env->Directory() == datadir); +} + +BOOST_AUTO_TEST_CASE(getwalletenv_directory) +{ + std::string expected_name = "wallet.dat"; + fs::path datadir = SetDataDir("tempdir"); + + std::string filename; + std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename); + BOOST_CHECK(filename == expected_name); + BOOST_CHECK(env->Directory() == datadir); +} + +BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple) +{ + fs::path datadir = SetDataDir("tempdir"); + fs::path datadir_2 = SetDataDir("tempdir_2"); + std::string filename; + + std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename); + std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename); + std::shared_ptr<BerkeleyEnvironment> env_3 = GetWalletEnv(datadir_2, filename); + + BOOST_CHECK(env_1 == env_2); + BOOST_CHECK(env_2 != env_3); +} + +BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance) +{ + fs::path datadir = SetDataDir("tempdir"); + fs::path datadir_2 = SetDataDir("tempdir_2"); + std::string filename; + + std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename); + std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename); + env_1_a.reset(); + + std::shared_ptr<BerkeleyEnvironment> env_1_b = GetWalletEnv(datadir, filename); + std::shared_ptr<BerkeleyEnvironment> env_2_b = GetWalletEnv(datadir_2, filename); + + BOOST_CHECK(env_1_a != env_1_b); + BOOST_CHECK(env_2_a == env_2_b); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index cd47b31da1..0f2d9fbd3d 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -6,7 +6,7 @@ #define BITCOIN_WALLET_TEST_INIT_TEST_FIXTURE_H #include <interfaces/chain.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> struct InitWalletDirTestingSetup: public BasicTestingSetup { diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 5852d3ef84..e1c53c83e2 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -4,7 +4,7 @@ #include <boost/test/unit_test.hpp> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/init_test_fixture.h> #include <init.h> diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 9918eeb89f..f774cb4ad1 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -4,13 +4,15 @@ #include <key_io.h> #include <script/sign.h> +#include <util/bip32.h> #include <util/strencodings.h> +#include <wallet/psbtwallet.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <univalue.h> #include <boost/test/unit_test.hpp> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <wallet/test/wallet_test_fixture.h> BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) @@ -60,7 +62,8 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) ssData >> psbtx; // Fill transaction with our data - FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true); + bool complete = true; + BOOST_REQUIRE_EQUAL(TransactionError::OK, FillPSBT(&m_wallet, psbtx, complete, SIGHASH_ALL, false, true)); // Get the final tx CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index ae7092fa89..acc61c984f 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <util/strencodings.h> #include <wallet/crypter.h> diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index a5fb1db86c..6526e69eea 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -8,17 +8,13 @@ #include <wallet/db.h> #include <wallet/rpcwallet.h> -WalletTestingSetup::WalletTestingSetup(const std::string& chainName): - TestingSetup(chainName), m_wallet(*m_chain, WalletLocation(), WalletDatabase::CreateMock()) +WalletTestingSetup::WalletTestingSetup(const std::string& chainName) + : TestingSetup(chainName), + m_wallet(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()) { bool fFirstRun; m_wallet.LoadWallet(fFirstRun); - RegisterValidationInterface(&m_wallet); + m_wallet.handleNotifications(); - RegisterWalletRPCCommands(tableRPC); -} - -WalletTestingSetup::~WalletTestingSetup() -{ - UnregisterValidationInterface(&m_wallet); + m_chain_client->registerRpcs(); } diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index e6fe8c9473..1017e61700 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <interfaces/chain.h> #include <interfaces/wallet.h> @@ -17,9 +17,9 @@ */ struct WalletTestingSetup: public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); - ~WalletTestingSetup(); std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(); + 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 0db22cf6fe..3cdbde33c3 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -13,7 +13,7 @@ #include <consensus/validation.h> #include <interfaces/chain.h> #include <rpc/server.h> -#include <test/test_bitcoin.h> +#include <test/setup_common.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/test/wallet_test_fixture.h> @@ -39,39 +39,41 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) auto chain = interfaces::MakeChain(); // Cap last block file size, and mine new block in a new block file. - const CBlockIndex* const null_block = nullptr; CBlockIndex* oldTip = chainActive.Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); + LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Verify ScanForWalletTransactions accommodates a null start block. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(failed_block, null_block); - BOOST_CHECK_EQUAL(stop_block, null_block); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK(result.last_failed_block.IsNull()); + BOOST_CHECK(result.last_scanned_block.IsNull()); + BOOST_CHECK(!result.last_scanned_height); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(failed_block, null_block); - BOOST_CHECK_EQUAL(stop_block, newTip); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 100 * COIN); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK(result.last_failed_block.IsNull()); + BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); } // Prune the older block file. @@ -81,15 +83,16 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); - BOOST_CHECK_EQUAL(failed_block, oldTip); - BOOST_CHECK_EQUAL(stop_block, newTip); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); + BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); } // Prune the remaining block file. @@ -98,15 +101,16 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions scans no blocks. { - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); - BOOST_CHECK_EQUAL(failed_block, newTip); - BOOST_CHECK_EQUAL(stop_block, null_block); - BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); + BOOST_CHECK(result.last_scanned_block.IsNull()); + BOOST_CHECK(!result.last_scanned_height); + BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); } } @@ -120,6 +124,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex* newTip = chainActive.Tip(); + LockAnnotation lock(::cs_main); auto locked_chain = chain->lock(); // Prune the older block file. @@ -130,7 +135,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); AddWallet(wallet); UniValue keys; keys.setArray(); @@ -193,7 +198,7 @@ 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, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME; wallet->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); @@ -209,7 +214,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); JSONRPCRequest request; request.params.setArray(); @@ -240,7 +245,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { auto chain = interfaces::MakeChain(); - CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CWalletTx wtx(&wallet, m_coinbase_txns.back()); auto locked_chain = chain->lock(); LOCK(wallet.cs_wallet); @@ -265,6 +270,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 SetMockTime(mockTime); CBlockIndex* block = nullptr; if (blockTime > 0) { + LockAnnotation lock(::cs_main); auto locked_chain = wallet.chain().lock(); auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); @@ -276,7 +282,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 CWalletTx wtx(&wallet, MakeTransactionRef(tx)); if (block) { - wtx.SetMerkleBranch(block, 0); + wtx.SetMerkleBranch(block->GetBlockHash(), 0); } { LOCK(cs_main); @@ -334,17 +340,17 @@ public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - wallet = MakeUnique<CWallet>(*m_chain, WalletLocation(), WalletDatabase::CreateMock()); + wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(wallet.get()); reserver.reserve(); - const CBlockIndex* const null_block = nullptr; - const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; - BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); - BOOST_CHECK_EQUAL(stop_block, chainActive.Tip()); - BOOST_CHECK_EQUAL(failed_block, null_block); + CWallet::ScanResult result = wallet->ScanForWalletTransactions(chainActive.Genesis()->GetBlockHash(), {} /* stop_block */, reserver, false /* update */); + BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(result.last_scanned_block, chainActive.Tip()->GetBlockHash()); + BOOST_CHECK_EQUAL(*result.last_scanned_height, chainActive.Height()); + BOOST_CHECK(result.last_failed_block.IsNull()); } ~ListCoinsTestingSetup() @@ -362,7 +368,7 @@ public: CCoinControl dummy; BOOST_CHECK(wallet->CreateTransaction(*m_locked_chain, {recipient}, tx, reservekey, fee, changePos, error, dummy)); CValidationState state; - BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, nullptr, state)); + BOOST_CHECK(wallet->CommitTransaction(tx, {}, {}, reservekey, state)); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); @@ -372,7 +378,7 @@ public: LOCK(wallet->cs_wallet); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetMerkleBranch(chainActive.Tip(), 1); + it->second.SetMerkleBranch(chainActive.Tip()->GetBlockHash(), 1); return it->second; } @@ -445,7 +451,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { auto chain = interfaces::MakeChain(); - std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + 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); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 536429aeac..4737e2f6f4 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,17 +5,16 @@ #include <wallet/wallet.h> -#include <checkpoints.h> #include <chain.h> -#include <wallet/coincontrol.h> +#include <checkpoints.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> #include <interfaces/chain.h> +#include <interfaces/wallet.h> #include <key.h> #include <key_io.h> #include <keystore.h> -#include <validation.h> #include <net.h> #include <policy/fees.h> #include <policy/policy.h> @@ -27,7 +26,14 @@ #include <shutdown.h> #include <timedata.h> #include <txmempool.h> +#include <util/bip32.h> +#include <util/error.h> +#include <util/fees.h> #include <util/moneystr.h> +#include <util/rbf.h> +#include <util/validation.h> +#include <validation.h> +#include <wallet/coincontrol.h> #include <wallet/fees.h> #include <algorithm> @@ -82,13 +88,74 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) return nullptr; } +static Mutex g_wallet_release_mutex; +static std::condition_variable g_wallet_release_cv; +static std::set<CWallet*> g_unloading_wallet_set; + // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet* wallet) { + // Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain + // so that it's in sync with the current chainstate. wallet->WalletLogPrintf("Releasing wallet\n"); wallet->BlockUntilSyncedToCurrentChain(); wallet->Flush(); + wallet->m_chain_notifications_handler.reset(); delete wallet; + // Wallet is now released, notify UnloadWallet, if any. + { + LOCK(g_wallet_release_mutex); + if (g_unloading_wallet_set.erase(wallet) == 0) { + // UnloadWallet was not called for this wallet, all done. + return; + } + } + g_wallet_release_cv.notify_all(); +} + +void UnloadWallet(std::shared_ptr<CWallet>&& wallet) +{ + // Mark wallet for unloading. + CWallet* pwallet = wallet.get(); + { + LOCK(g_wallet_release_mutex); + auto it = g_unloading_wallet_set.insert(pwallet); + assert(it.second); + } + // The wallet can be in use so it's not possible to explicitly unload here. + // Notify the unload intent so that all remaining shared pointers are + // released. + pwallet->NotifyUnload(); + // Time to ditch our shared_ptr and wait for ReleaseWallet call. + wallet.reset(); + { + WAIT_LOCK(g_wallet_release_mutex, lock); + while (g_unloading_wallet_set.count(pwallet) == 1) { + g_wallet_release_cv.wait(lock); + } + } +} + +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning) +{ + if (!CWallet::Verify(chain, location, false, error, warning)) { + error = "Wallet file verification failed: " + error; + return nullptr; + } + + std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location); + if (!wallet) { + error = "Wallet loading failed."; + return nullptr; + } + AddWallet(wallet); + wallet->postInitProcess(); + return wallet; +} + +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning) +{ + return LoadWallet(chain, WalletLocation(name), error, warning); } const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; @@ -129,7 +196,8 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); - AssertLockHeld(cs_wallet); // mapKeyMetadata + 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; @@ -138,7 +206,7 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); - // use HD key derivation if HD was enabled during wallet creation + // 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 { @@ -193,24 +261,36 @@ void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey 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) +bool CWallet::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) { - AssertLockHeld(cs_wallet); // mapKeyMetadata + AssertLockHeld(cs_wallet); + + // Make sure we aren't adding private keys to private key disabled wallets + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey // which is overridden below. To avoid flushes, the database handle is @@ -241,6 +321,7 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); return true; } @@ -268,20 +349,68 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, } } -void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &meta) +void CWallet::LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata& meta) { - AssertLockHeld(cs_wallet); // mapKeyMetadata + AssertLockHeld(cs_wallet); UpdateTimeFirstKey(meta.nCreateTime); mapKeyMetadata[keyID] = meta; } -void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &meta) +void CWallet::LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata& meta) { - AssertLockHeld(cs_wallet); // m_script_metadata + AssertLockHeld(cs_wallet); UpdateTimeFirstKey(meta.nCreateTime); m_script_metadata[script_id] = meta; } +// Writes a keymetadata for a public key. overwrite specifies whether to overwrite an existing metadata for that key if there exists one. +bool CWallet::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) +{ + return WalletBatch(*database).WriteKeyMetadata(meta, pubkey, overwrite); +} + +void CWallet::UpgradeKeyMetadata() +{ + AssertLockHeld(cs_wallet); + if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + return; + } + + std::unique_ptr<WalletBatch> batch = MakeUnique<WalletBatch>(*database); + size_t cnt = 0; + 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); + if (++cnt % 1000 == 0) { + // avoid creating overlarge in-memory batches in case the wallet contains large amounts of keys + batch.reset(new WalletBatch(*database)); + } + } + } + } + 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 CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret); @@ -307,7 +436,11 @@ bool CWallet::AddCScript(const CScript& redeemScript) { if (!CCryptoKeyStore::AddCScript(redeemScript)) return false; - return WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript); + if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript)) { + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; } bool CWallet::LoadCScript(const CScript& redeemScript) @@ -332,7 +465,11 @@ bool CWallet::AddWatchOnly(const CScript& dest) const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); - return WalletBatch(*database).WriteWatchOnly(dest, meta); + if (WalletBatch(*database).WriteWatchOnly(dest, meta)) { + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; } bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime) @@ -359,7 +496,7 @@ bool CWallet::LoadWatchOnly(const CScript &dest) return CCryptoKeyStore::AddWatchOnly(dest); } -bool CWallet::Unlock(const SecureString& strWalletPassphrase) +bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) { CCrypter crypter; CKeyingMaterial _vMasterKey; @@ -372,8 +509,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) continue; // try another master key - if (CCryptoKeyStore::Unlock(_vMasterKey)) + if (CCryptoKeyStore::Unlock(_vMasterKey, accept_no_keys)) { + // Now that we've unlocked, upgrade the key metadata + UpgradeKeyMetadata(); return true; + } } } return false; @@ -433,7 +573,7 @@ void CWallet::ChainStateFlushed(const CBlockLocator& loc) void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit) { - LOCK(cs_wallet); // nWalletVersion + LOCK(cs_wallet); if (nWalletVersion >= nVersion) return; @@ -457,7 +597,7 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool CWallet::SetMaxVersion(int nVersion) { - LOCK(cs_wallet); // nWalletVersion, nWalletMaxVersion + LOCK(cs_wallet); // cannot downgrade below current version if (nWalletVersion > nVersion) return false; @@ -741,9 +881,9 @@ DBErrors CWallet::ReorderTransactions() return DBErrors::LOAD_OK; } -int64_t CWallet::IncOrderPosNext(WalletBatch *batch) +int64_t CWallet::IncOrderPosNext(WalletBatch* batch) { - AssertLockHeld(cs_wallet); // nOrderPosNext + AssertLockHeld(cs_wallet); int64_t nRet = nOrderPosNext++; if (batch) { batch->WriteOrderPosNext(nOrderPosNext); @@ -805,7 +945,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) { - wtx.nTimeReceived = GetAdjustedTime(); + wtx.nTimeReceived = chain().getAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx); @@ -896,19 +1036,19 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn) } } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate) { const CTransaction& tx = *ptx; { AssertLockHeld(cs_wallet); - if (pIndex != nullptr) { + if (!block_hash.IsNull()) { for (const CTxIn& txin : tx.vin) { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pIndex->GetBlockHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); - MarkConflicted(pIndex->GetBlockHash(), range.first->second); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + MarkConflicted(block_hash, range.first->second); } range.first++; } @@ -944,8 +1084,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI CWalletTx wtx(this, ptx); // Get merkle branch if transaction was found in a block - if (pIndex != nullptr) - wtx.SetMerkleBranch(pIndex, posInBlock); + if (!block_hash.IsNull()) + wtx.SetMerkleBranch(block_hash, posInBlock); return AddToWallet(wtx, false); } @@ -1032,11 +1172,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) auto locked_chain = chain().lock(); LOCK(cs_wallet); - int conflictconfirms = 0; - CBlockIndex* pindex = LookupBlockIndex(hashBlock); - if (pindex && chainActive.Contains(pindex)) { - conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1); - } + int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); // If number of conflict confirms cannot be determined, this means // that the block is still unknown or not yet part of the main chain, // for example when loading the wallet during a reindex. Do nothing in that @@ -1082,8 +1218,8 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, update_tx)) +void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) { + if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1095,7 +1231,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pin void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx); + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); auto it = mapWallet.find(ptx->GetHash()); if (it != mapWallet.end()) { @@ -1111,7 +1247,8 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { } } -void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) { +void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) { + const uint256& block_hash = block.GetHash(); auto locked_chain = chain().lock(); LOCK(cs_wallet); // TODO: Temporarily ensure that mempool removals are notified before @@ -1123,30 +1260,33 @@ void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const // the notification that the conflicted transaction was evicted. for (const CTransactionRef& ptx : vtxConflicted) { - SyncTransaction(ptx); + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); TransactionRemovedFromMempool(ptx); } - for (size_t i = 0; i < pblock->vtx.size(); i++) { - SyncTransaction(pblock->vtx[i], pindex, i); - TransactionRemovedFromMempool(pblock->vtx[i]); + for (size_t i = 0; i < block.vtx.size(); i++) { + SyncTransaction(block.vtx[i], block_hash, i); + TransactionRemovedFromMempool(block.vtx[i]); } - m_last_block_processed = pindex; + m_last_block_processed = block_hash; } -void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) { +void CWallet::BlockDisconnected(const CBlock& block) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - for (const CTransactionRef& ptx : pblock->vtx) { - SyncTransaction(ptx); + for (const CTransactionRef& ptx : block.vtx) { + SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */); } } +void CWallet::UpdatedBlockTip() +{ + m_best_block_time = GetTime(); +} void CWallet::BlockUntilSyncedToCurrentChain() { - AssertLockNotHeld(cs_main); AssertLockNotHeld(cs_wallet); { @@ -1156,9 +1296,8 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // protected by cs_wallet instead of cs_main, but as long as we need // cs_main here anyway, it's easier to just call it cs_main-protected. auto locked_chain = chain().lock(); - const CBlockIndex* initialChainTip = chainActive.Tip(); - if (m_last_block_processed && m_last_block_processed->GetAncestor(initialChainTip->nHeight) == initialChainTip) { + if (!m_last_block_processed.IsNull() && locked_chain->isPotentialTip(m_last_block_processed)) { return; } } @@ -1166,7 +1305,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // ...otherwise put a callback in the validation interface queue and wait // for the queue to drain enough to execute it (indicating we are caught up // at least with the time we entered this function). - SyncWithValidationInterfaceQueue(); + chain().waitForNotifications(); } @@ -1338,6 +1477,7 @@ CPubKey CWallet::DeriveNewSeed(const CKey& key) // 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(); { @@ -1364,6 +1504,8 @@ void CWallet::SetHDSeed(const CPubKey& seed) 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) @@ -1380,6 +1522,30 @@ 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 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(); + } + return keypool_has_keys; +} + void CWallet::SetWalletFlag(uint64_t flags) { LOCK(cs_wallet); @@ -1388,6 +1554,14 @@ void CWallet::SetWalletFlag(uint64_t flags) throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } +void CWallet::UnsetWalletFlag(uint64_t flag) +{ + LOCK(cs_wallet); + m_wallet_flags &= ~flag; + if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) + throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); +} + bool CWallet::IsWalletFlagSet(uint64_t flag) { return (m_wallet_flags & flag); @@ -1471,7 +1645,7 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall // implies that we can sign for every input. return -1; } - return GetVirtualTransactionSize(txNew); + return GetVirtualTransactionSize(CTransaction(txNew)); } int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) @@ -1552,141 +1726,153 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. - CBlockIndex* startBlock = nullptr; + uint256 start_block; { auto locked_chain = chain().lock(); - startBlock = chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW); - WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0); + const Optional<int> start_height = locked_chain->findFirstBlockWithTime(startTime - TIMESTAMP_WINDOW, &start_block); + const Optional<int> tip_height = locked_chain->getHeight(); + WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, tip_height && start_height ? *tip_height - *start_height + 1 : 0); } - if (startBlock) { - const CBlockIndex *failedBlock, *stop_block; + if (!start_block.IsNull()) { // TODO: this should take into account failure by ScanResult::USER_ABORT - if (ScanResult::FAILURE == ScanForWalletTransactions(startBlock, nullptr, reserver, failedBlock, stop_block, update)) { - return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1; + ScanResult result = ScanForWalletTransactions(start_block, {} /* stop_block */, reserver, update); + if (result.status == ScanResult::FAILURE) { + int64_t time_max; + if (!chain().findBlock(result.last_failed_block, nullptr /* block */, nullptr /* time */, &time_max)) { + throw std::logic_error("ScanForWalletTransactions returned invalid block hash"); + } + return time_max + TIMESTAMP_WINDOW + 1; } } return startTime; } /** - * Scan the block chain (starting in pindexStart) for transactions + * Scan the block chain (starting in start_block) for transactions * from or to us. If fUpdate is true, found transactions that already * exist in the wallet will be updated. * - * @param[in] pindexStop if not a nullptr, the scan will stop at this block-index - * @param[out] failed_block if FAILURE is returned, the most recent block - * that could not be scanned, otherwise nullptr - * @param[out] stop_block the most recent block that could be scanned, - * otherwise nullptr if no block could be scanned + * @param[in] start_block Scan starting block. If block is not on the active + * chain, the scan will return SUCCESS immediately. + * @param[in] stop_block Scan ending block. If block is not on the active + * chain, the scan will continue until it reaches the + * chain tip. * - * @return ScanResult indicating success or failure of the scan. SUCCESS if - * scan was successful. FAILURE if a complete rescan was not possible (due to - * pruning or corruption). USER_ABORT if the rescan was aborted before it - * could complete. + * @return ScanResult returning scan information and indicating success or + * failure. Return status will be set to SUCCESS if scan was + * successful. FAILURE if a complete rescan was not possible (due to + * pruning or corruption). USER_ABORT if the rescan was aborted before + * it could complete. * - * @pre Caller needs to make sure pindexStop (and the optional pindexStart) are on + * @pre Caller needs to make sure start_block (and the optional stop_block) are on * the main chain after to the addition of any new keys you want to detect * transactions for. */ -CWallet::ScanResult CWallet::ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate) +CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, const uint256& stop_block, const WalletRescanReserver& reserver, bool fUpdate) { int64_t nNow = GetTime(); - const CChainParams& chainParams = Params(); assert(reserver.isReserved()); - if (pindexStop) { - assert(pindexStop->nHeight >= pindexStart->nHeight); - } - const CBlockIndex* pindex = pindexStart; - failed_block = nullptr; - stop_block = nullptr; + uint256 block_hash = start_block; + ScanResult result; - if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); + WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); { fAbortRescan = false; ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup - CBlockIndex* tip = nullptr; + uint256 tip_hash; + // The way the 'block_height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. + Optional<int> block_height = MakeOptional(false, int()); double progress_begin; double progress_end; { auto locked_chain = chain().lock(); - progress_begin = GuessVerificationProgress(chainParams.TxData(), pindex); - if (pindexStop == nullptr) { - tip = chainActive.Tip(); - progress_end = GuessVerificationProgress(chainParams.TxData(), tip); - } else { - progress_end = GuessVerificationProgress(chainParams.TxData(), pindexStop); + if (Optional<int> tip_height = locked_chain->getHeight()) { + tip_hash = locked_chain->getBlockHash(*tip_height); } + block_height = locked_chain->getBlockHeight(block_hash); + progress_begin = chain().guessVerificationProgress(block_hash); + progress_end = chain().guessVerificationProgress(stop_block.IsNull() ? tip_hash : stop_block); } double progress_current = progress_begin; - while (pindex && !fAbortRescan && !ShutdownRequested()) { - if (pindex->nHeight % 100 == 0 && progress_end - progress_begin > 0.0) { + while (block_height && !fAbortRescan && !chain().shutdownRequested()) { + if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), std::max(1, std::min(99, (int)((progress_current - progress_begin) / (progress_end - progress_begin) * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); - WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, progress_current); + WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current); } CBlock block; - if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) { + if (chain().findBlock(block_hash, &block) && !block.IsNull()) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - if (pindex && !chainActive.Contains(pindex)) { + if (!locked_chain->getBlockHeight(block_hash)) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. - failed_block = pindex; + // TODO: This should return success instead of failure, see + // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], pindex, posInBlock, fUpdate); + SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); } // scan succeeded, record block as most recent successfully scanned - stop_block = pindex; + result.last_scanned_block = block_hash; + result.last_scanned_height = *block_height; } else { // could not scan block, keep scanning but record this block as the most recent failure - failed_block = pindex; + result.last_failed_block = block_hash; + result.status = ScanResult::FAILURE; } - if (pindex == pindexStop) { + if (block_hash == stop_block) { break; } { auto locked_chain = chain().lock(); - pindex = chainActive.Next(pindex); - progress_current = GuessVerificationProgress(chainParams.TxData(), pindex); - if (pindexStop == nullptr && tip != chainActive.Tip()) { - tip = chainActive.Tip(); + Optional<int> tip_height = locked_chain->getHeight(); + if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) { + // break successfully when rescan has reached the tip, or + // previous block is no longer on the chain due to a reorg + break; + } + + // increment block and verification progress + block_hash = locked_chain->getBlockHash(++*block_height); + progress_current = chain().guessVerificationProgress(block_hash); + + // handle updated tip hash + const uint256 prev_tip_hash = tip_hash; + tip_hash = locked_chain->getBlockHash(*tip_height); + if (stop_block.IsNull() && prev_tip_hash != tip_hash) { // in case the tip has changed, update progress max - progress_end = GuessVerificationProgress(chainParams.TxData(), tip); + progress_end = chain().guessVerificationProgress(tip_hash); } } } ShowProgress(strprintf("%s " + _("Rescanning..."), GetDisplayName()), 100); // hide progress dialog in GUI - if (pindex && fAbortRescan) { - WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; - } else if (pindex && ShutdownRequested()) { - WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", pindex->nHeight, progress_current); - return ScanResult::USER_ABORT; + if (block_height && fAbortRescan) { + WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current); + result.status = ScanResult::USER_ABORT; + } else if (block_height && chain().shutdownRequested()) { + WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", *block_height, progress_current); + result.status = ScanResult::USER_ABORT; } } - if (failed_block) { - return ScanResult::FAILURE; - } else { - return ScanResult::SUCCESS; - } + return result; } -void CWallet::ReacceptWalletTransactions() +void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) { // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) return; - auto locked_chain = chain().lock(); - LOCK(cs_wallet); std::map<int64_t, CWalletTx*> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion order @@ -1696,7 +1882,7 @@ void CWallet::ReacceptWalletTransactions() CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(*locked_chain); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1707,30 +1893,31 @@ void CWallet::ReacceptWalletTransactions() for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); CValidationState state; - wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state); + wtx.AcceptToMemoryPool(locked_chain, state); } } -bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman) +bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain) { - assert(pwallet->GetBroadcastTransactions()); - if (!IsCoinBase() && !isAbandoned() && GetDepthInMainChain(locked_chain) == 0) - { - CValidationState state; - /* GetDepthInMainChain already catches known conflicts. */ - if (InMempool() || AcceptToMemoryPool(locked_chain, maxTxFee, state)) { - pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); - if (connman) { - CInv inv(MSG_TX, GetHash()); - connman->ForEachNode([&inv](CNode* pnode) - { - pnode->PushInventory(inv); - }); - return true; - } - } - } - return false; + // Can't relay if wallet is not broadcasting + if (!pwallet->GetBroadcastTransactions()) return false; + // Don't relay coinbase transactions outside blocks + if (IsCoinBase()) return false; + // Don't relay abandoned transactions + if (isAbandoned()) return false; + // Don't relay conflicted or already confirmed transactions + if (GetDepthInMainChain(locked_chain) != 0) return false; + // Don't relay transactions that aren't accepted to the mempool + CValidationState unused_state; + if (!InMempool() && !AcceptToMemoryPool(locked_chain, unused_state)) return false; + // Don't try to relay if the node is not connected to the p2p network + if (!pwallet->chain().p2pEnabled()) return false; + + // Try to relay the transaction + pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString()); + pwallet->chain().relayTransaction(GetHash()); + + return true; } std::set<uint256> CWalletTx::GetConflicts() const @@ -1896,11 +2083,10 @@ bool CWalletTx::InMempool() const bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const { - LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. - // Quick answer in most cases - if (!CheckFinalTx(*tx)) + if (!locked_chain.checkFinalTx(*tx)) { return false; + } int nDepth = GetDepthInMainChain(locked_chain); if (nDepth >= 1) return true; @@ -1936,185 +2122,95 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const return CTransaction(tx1) == CTransaction(tx2); } -std::vector<uint256> CWallet::ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman) +// Rebroadcast transactions from the wallet. We do this on a random timer +// to slightly obfuscate which transactions come from our wallet. +// +// Ideally, we'd only resend transactions that we think should have been +// mined in the most recent block. Any transaction that wasn't in the top +// blockweight of transactions in the mempool shouldn't have been mined, +// and so is probably just sitting in the mempool waiting to be confirmed. +// Rebroadcasting does nothing to speed up confirmation and only damages +// privacy. +void CWallet::ResendWalletTransactions() { - std::vector<uint256> result; - - LOCK(cs_wallet); + // During reindex, importing and IBD, old wallet transactions become + // unconfirmed. Don't resend them as that would spam other nodes. + if (!chain().isReadyToBroadcast()) return; - // Sort them in chronological order - std::multimap<unsigned int, CWalletTx*> mapSorted; - for (std::pair<const uint256, CWalletTx>& item : mapWallet) - { - CWalletTx& wtx = item.second; - // Don't rebroadcast if newer than nTime: - if (wtx.nTimeReceived > nTime) - continue; - mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx)); - } - for (const std::pair<const unsigned int, CWalletTx*>& item : mapSorted) - { - CWalletTx& wtx = *item.second; - if (wtx.RelayWalletTransaction(locked_chain, connman)) - result.push_back(wtx.GetHash()); - } - return result; -} - -void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) -{ // Do this infrequently and randomly to avoid giving away // that these are our transactions. - if (GetTime() < nNextResend || !fBroadcastTransactions) - return; + if (GetTime() < nNextResend || !fBroadcastTransactions) return; bool fFirst = (nNextResend == 0); nNextResend = GetTime() + GetRand(30 * 60); - if (fFirst) - return; + if (fFirst) return; // Only do it if there's been a new block since last time - if (nBestBlockTime < nLastResend) - return; + if (m_best_block_time < nLastResend) return; nLastResend = GetTime(); - // Rebroadcast unconfirmed txes older than 5 minutes before the last - // block was found: - auto locked_chain = chain().assumeLocked(); // Temporary. Removed in upcoming lock cleanup - std::vector<uint256> relayed = ResendWalletTransactionsBefore(*locked_chain, nBestBlockTime-5*60, connman); - if (!relayed.empty()) - WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size()); -} - -/** @} */ // end of mapWallet - - - + int relayed_tx_count = 0; -/** @defgroup Actions - * - * @{ - */ - - -CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const -{ - CAmount nTotal = 0; - { + { // locked_chain and cs_wallet scope auto locked_chain = chain().lock(); LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) >= min_depth) { - nTotal += pcoin->GetAvailableCredit(*locked_chain, true, filter); - } - } - } - - return nTotal; -} -CAmount CWallet::GetUnconfirmedBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(*locked_chain); + // Relay transactions + for (std::pair<const uint256, CWalletTx>& item : mapWallet) { + CWalletTx& wtx = item.second; + // only rebroadcast unconfirmed txes older than 5 minutes before the + // last block was found + if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; + if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count; } - } - return nTotal; -} + } // locked_chain and cs_wallet -CAmount CWallet::GetImmatureBalance() const -{ - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureCredit(*locked_chain); - } + if (relayed_tx_count > 0) { + WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count); } - return nTotal; } -CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const +/** @} */ // end of mapWallet + +void MaybeResendWalletTxs() { - CAmount nTotal = 0; - { - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - for (const auto& entry : mapWallet) - { - const CWalletTx* pcoin = &entry.second; - if (!pcoin->IsTrusted(*locked_chain) && pcoin->GetDepthInMainChain(*locked_chain) == 0 && pcoin->InMempool()) - nTotal += pcoin->GetAvailableCredit(*locked_chain, true, ISMINE_WATCH_ONLY); - } + for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) { + pwallet->ResendWalletTransactions(); } - return nTotal; } -CAmount CWallet::GetImmatureWatchOnlyBalance() const + +/** @defgroup Actions + * + * @{ + */ + + +CWallet::Balance CWallet::GetBalance(const int min_depth) const { - CAmount nTotal = 0; + Balance ret; { auto locked_chain = chain().lock(); LOCK(cs_wallet); for (const auto& entry : mapWallet) { - const CWalletTx* pcoin = &entry.second; - nTotal += pcoin->GetImmatureWatchOnlyCredit(*locked_chain); - } - } - return nTotal; -} - -// Calculate total balance in a different way from GetBalance. The biggest -// difference is that GetBalance sums up all unspent TxOuts paying to the -// wallet, while this sums up both spent and unspent TxOuts paying to the -// wallet, and then subtracts the values of TxIns spending from the wallet. This -// also has fewer restrictions on which unconfirmed transactions are considered -// trusted. -CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth) const -{ - LockAnnotation lock(::cs_main); // Temporary, for CheckFinalTx below. Removed in upcoming commit. - auto locked_chain = chain().lock(); - LOCK(cs_wallet); - - CAmount balance = 0; - for (const auto& entry : mapWallet) { - const CWalletTx& wtx = entry.second; - const int depth = wtx.GetDepthInMainChain(*locked_chain); - if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase(*locked_chain)) { - continue; - } - - // Loop through tx outputs and add incoming payments. For outgoing txs, - // treat change outputs specially, as part of the amount debited. - CAmount debit = wtx.GetDebit(filter); - const bool outgoing = debit > 0; - for (const CTxOut& out : wtx.tx->vout) { - if (outgoing && IsChange(out)) { - debit -= out.nValue; - } else if (IsMine(out) & filter && depth >= minDepth) { - balance += out.nValue; + const CWalletTx& wtx = entry.second; + const bool is_trusted{wtx.IsTrusted(*locked_chain)}; + const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; + const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE)}; + const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY)}; + if (is_trusted && tx_depth >= min_depth) { + ret.m_mine_trusted += tx_credit_mine; + ret.m_watchonly_trusted += tx_credit_watchonly; } - } - - // For outgoing txs, subtract amount debited. - if (outgoing) { - balance -= debit; + if (!is_trusted && tx_depth == 0 && wtx.InMempool()) { + ret.m_mine_untrusted_pending += tx_credit_mine; + ret.m_watchonly_untrusted_pending += tx_credit_watchonly; + } + ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); + ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); } } - - return balance; + return ret; } CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const @@ -2135,7 +2231,6 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const CAmount &nMinimumAmount, const CAmount &nMaximumAmount, const CAmount &nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const { - AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); vCoins.clear(); @@ -2144,24 +2239,25 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< for (const auto& entry : mapWallet) { const uint256& wtxid = entry.first; - const CWalletTx* pcoin = &entry.second; + const CWalletTx& wtx = entry.second; - if (!CheckFinalTx(*pcoin->tx)) + if (!locked_chain.checkFinalTx(*wtx.tx)) { continue; + } - if (pcoin->IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < 0) continue; // We should not consider coins which aren't at least in our mempool // It's possible for these to be conflicted via ancestors which we may never be able to detect - if (nDepth == 0 && !pcoin->InMempool()) + if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = pcoin->IsTrusted(locked_chain); + bool safeTx = wtx.IsTrusted(locked_chain); // We should not consider coins from transactions that are replacing // other transactions. @@ -2178,7 +2274,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // be a 1-block reorg away from the chain where transactions A and C // were accepted to another chain where B, B', and C were all // accepted. - if (nDepth == 0 && pcoin->mapValue.count("replaces_txid")) { + if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) { safeTx = false; } @@ -2190,7 +2286,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< // intending to replace A', but potentially resulting in a scenario // where A, A', and D could all be accepted (instead of just B and // D, or just A and A' like the user would want). - if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) { + if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { safeTx = false; } @@ -2201,8 +2297,8 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (nDepth < nMinDepth || nDepth > nMaxDepth) continue; - for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) { - if (pcoin->tx->vout[i].nValue < nMinimumAmount || pcoin->tx->vout[i].nValue > nMaximumAmount) + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) continue; if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) @@ -2214,20 +2310,20 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (IsSpent(locked_chain, wtxid, i)) continue; - isminetype mine = IsMine(pcoin->tx->vout[i]); + isminetype mine = IsMine(wtx.tx->vout[i]); if (mine == ISMINE_NO) { continue; } - bool solvable = IsSolvable(*this, pcoin->tx->vout[i].scriptPubKey); + bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - vCoins.push_back(COutput(pcoin, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { - nTotal += pcoin->tx->vout[i].nValue; + nTotal += wtx.tx->vout[i].nValue; if (nTotal >= nMinimumSumAmount) { return; @@ -2244,7 +2340,6 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock& locked_chain) const { - AssertLockHeld(cs_main); AssertLockHeld(cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; @@ -2309,10 +2404,10 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil FeeCalculation feeCalc; CCoinControl temp; temp.m_confirm_target = 1008; - CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, ::mempool, ::feeEstimator, &feeCalc); + CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc); // Calculate cost of change - CAmount cost_of_change = GetDiscardRate(*this, ::feeEstimator).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size); + CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size); // Filter by the min conf specs and add to utxo_pool and calculate effective value for (OutputGroup& group : groups) { @@ -2386,13 +2481,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { - const CWalletTx* pcoin = &it->second; + const CWalletTx& wtx = it->second; // Clearly invalid input, fail - if (pcoin->tx->vout.size() <= outpoint.n) + if (wtx.tx->vout.size() <= outpoint.n) return false; // Just to calculate the marginal byte size - nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(pcoin->tx, outpoint.n)); + nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue; + setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n)); } else return false; // TODO: Allow non-wallet inputs } @@ -2438,9 +2533,9 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm return res; } -bool CWallet::SignTransaction(CMutableTransaction &tx) +bool CWallet::SignTransaction(CMutableTransaction& tx) { - AssertLockHeld(cs_wallet); // mapWallet + AssertLockHeld(cs_wallet); // sign the new tx int nIn = 0; @@ -2516,13 +2611,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC return true; } -static bool IsCurrentForAntiFeeSniping(interfaces::Chain::Lock& locked_chain) +static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain) { - if (IsInitialBlockDownload()) { + if (chain.isInitialBlockDownload()) { return false; } constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds - if (chainActive.Tip()->GetBlockTime() < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { + if (locked_chain.getBlockTime(*locked_chain.getHeight()) < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { return false; } return true; @@ -2532,8 +2627,9 @@ static bool IsCurrentForAntiFeeSniping(interfaces::Chain::Lock& locked_chain) * Return a height-based locktime for new transactions (uses the height of the * current chain tip unless we are not synced with the current chain */ -static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_chain) +static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain) { + uint32_t const height = locked_chain.getHeight().get_value_or(-1); uint32_t locktime; // Discourage fee sniping. // @@ -2555,8 +2651,8 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_cha // enough, that fee sniping isn't a problem yet, but by implementing a fix // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. - if (IsCurrentForAntiFeeSniping(locked_chain)) { - locktime = chainActive.Height(); + if (IsCurrentForAntiFeeSniping(chain, locked_chain)) { + locktime = height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, @@ -2570,7 +2666,7 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain::Lock& locked_cha // unique "nLockTime fingerprint", set nLockTime to a constant. locktime = 0; } - assert(locktime <= (unsigned int)chainActive.Height()); + assert(locktime <= height); assert(locktime < LOCKTIME_THRESHOLD); return locktime; } @@ -2629,7 +2725,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std CMutableTransaction txNew; - txNew.nLockTime = GetLocktimeForNewTransaction(locked_chain); + txNew.nLockTime = GetLocktimeForNewTransaction(chain(), locked_chain); FeeCalculation feeCalc; CAmount nFeeNeeded; @@ -2640,7 +2736,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std LOCK(cs_wallet); { std::vector<COutput> vAvailableCoins; - AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control); + AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0, coin_control.m_min_depth); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -2660,8 +2756,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // post-backup change. // Reserve a new key pair from key pool - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet."); + if (!CanGetAddresses(true)) { + strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys."); return false; } CPubKey vchPubKey; @@ -2681,10 +2777,10 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std CTxOut change_prototype_txout(0, scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); - CFeeRate discard_rate = GetDiscardRate(*this, ::feeEstimator); + CFeeRate discard_rate = GetDiscardRate(*this); // Get the fee rate to use effective values in coin selection - CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc); + CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, &feeCalc); nFeeRet = 0; bool pick_new_inputs = true; @@ -2725,7 +2821,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // Include the fee cost for outputs. Note this is only used for BnB right now coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); - if (IsDust(txout, ::dustRelayFee)) + if (IsDust(txout, chain().relayDustFee())) { if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { @@ -2811,13 +2907,13 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); } - nBytes = CalculateMaximumSignedTxSize(txNew, this, coin_control.fAllowWatchOnly); + nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); if (nBytes < 0) { strFailReason = _("Signing transaction failed"); return false; } - nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc); + nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc); if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { // eventually allow a fallback fee strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); @@ -2826,7 +2922,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // If we made it here and we aren't even able to meet the relay fee on the next pass, give up // because we must be at the maximum allowed fee. - if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes)) + if (nFeeNeeded < chain().relayMinFee().GetFee(nBytes)) { strFailReason = _("Transaction too large for fee policy"); return false; @@ -2845,7 +2941,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // change output. Only try this once. if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; // Add 2 as a buffer in case increasing # of outputs changes compact size - CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, ::mempool, ::feeEstimator, nullptr); + CAmount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control, nullptr); CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, discard_rate); if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) { pick_new_inputs = false; @@ -2950,16 +3046,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits - LockPoints lp; - CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp); - CTxMemPool::setEntries setAncestors; - size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); - size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000; - size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); - size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000; - std::string errString; - LOCK(::mempool.cs); - if (!::mempool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) { + if (!chain().checkChainLimits(tx)) { strFailReason = _("Transaction has too long of a mempool chain"); return false; } @@ -2979,7 +3066,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std /** * Call after CreateTransaction unless you want to abort */ -bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state) +bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CValidationState& state) { { auto locked_chain = chain().lock(); @@ -3016,11 +3103,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve if (fBroadcastTransactions) { // Broadcast - if (!wtx.AcceptToMemoryPool(*locked_chain, maxTxFee, state)) { + if (!wtx.AcceptToMemoryPool(*locked_chain, state)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state)); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } else { - wtx.RelayWalletTransaction(*locked_chain, connman); + wtx.RelayWalletTransaction(*locked_chain); } } } @@ -3029,7 +3116,6 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); fFirstRunRet = false; @@ -3050,7 +3136,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK(cs_KeyStore); // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() + && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); } if (nLoadWalletRet != DBErrors::LOAD_OK) @@ -3061,8 +3148,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut) { - AssertLockHeld(cs_wallet); // mapWallet - DBErrors nZapSelectTxRet = WalletBatch(*database,"cr+").ZapSelectTx(vHashIn, vHashOut); + AssertLockHeld(cs_wallet); + DBErrors nZapSelectTxRet = WalletBatch(*database, "cr+").ZapSelectTx(vHashIn, vHashOut); for (uint256 hash : vHashOut) { const auto& it = mapWallet.find(hash); wtxOrdered.erase(it->second.m_it_wtxOrdered); @@ -3088,7 +3175,6 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 MarkDirty(); return DBErrors::LOAD_OK; - } DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) @@ -3119,7 +3205,7 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s { bool fUpdated = false; { - LOCK(cs_wallet); // mapAddressBook + LOCK(cs_wallet); std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address); fUpdated = mi != mapAddressBook.end(); mapAddressBook[address].name = strName; @@ -3136,7 +3222,7 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s bool CWallet::DelAddressBook(const CTxDestination& address) { { - LOCK(cs_wallet); // mapAddressBook + LOCK(cs_wallet); // Delete destdata tuples associated with address std::string strAddress = EncodeDestination(address); @@ -3208,7 +3294,7 @@ bool CWallet::NewKeyPool() size_t CWallet::KeypoolCountExternalKeys() { - AssertLockHeld(cs_wallet); // setExternalKeyPool + AssertLockHeld(cs_wallet); return setExternalKeyPool.size() + set_pre_split_keypool.size(); } @@ -3235,7 +3321,7 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) bool CWallet::TopUpKeyPool(unsigned int kpSize) { - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!CanGenerateKeys()) { return false; } { @@ -3269,28 +3355,40 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) internal = true; } - 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; - CPubKey pubkey(GenerateNewKey(batch, internal)); - if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { - throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); - } - - if (internal) { - setInternalKeyPool.insert(index); - } else { - setExternalKeyPool.insert(index); - } - m_pool_key_to_index[pubkey.GetID()] = index; + 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::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal) +{ + WalletBatch batch(*database); + AddKeypoolPubkeyWithDB(pubkey, internal, batch); + NotifyCanGetAddressesChanged(); +} + +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; @@ -3301,7 +3399,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe if (!IsLocked()) TopUpKeyPool(); - bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal; + 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; @@ -3318,7 +3417,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe if (!batch.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read failed"); } - if (!HaveKey(keypool.vchPubKey.GetID())) { + 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 @@ -3332,6 +3432,7 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); WalletLogPrintf("keypool reserve %d\n", nIndex); } + NotifyCanGetAddressesChanged(); return true; } @@ -3356,13 +3457,14 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) 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 (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!CanGetAddresses(internal)) { return false; } @@ -3370,7 +3472,7 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) { LOCK(cs_wallet); int64_t nIndex; - if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) { + if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (IsLocked()) return false; WalletBatch batch(*database); result = GenerateNewKey(batch, internal); @@ -3422,27 +3524,27 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: LOCK(cs_wallet); for (const auto& walletEntry : mapWallet) { - const CWalletTx *pcoin = &walletEntry.second; + const CWalletTx& wtx = walletEntry.second; - if (!pcoin->IsTrusted(locked_chain)) + if (!wtx.IsTrusted(locked_chain)) continue; - if (pcoin->IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase(locked_chain)) continue; - int nDepth = pcoin->GetDepthInMainChain(locked_chain); - if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) + int nDepth = wtx.GetDepthInMainChain(locked_chain); + if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; - for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; - if (!IsMine(pcoin->tx->vout[i])) + if (!IsMine(wtx.tx->vout[i])) continue; - if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) + if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue; + CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3456,19 +3558,19 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() { - AssertLockHeld(cs_wallet); // mapWallet + AssertLockHeld(cs_wallet); std::set< std::set<CTxDestination> > groupings; std::set<CTxDestination> grouping; for (const auto& walletEntry : mapWallet) { - const CWalletTx *pcoin = &walletEntry.second; + const CWalletTx& wtx = walletEntry.second; - if (pcoin->tx->vin.size() > 0) + if (wtx.tx->vin.size() > 0) { bool any_mine = false; // group all input addresses with each other - for (const CTxIn& txin : pcoin->tx->vin) + for (const CTxIn& txin : wtx.tx->vin) { CTxDestination address; if(!IsMine(txin)) /* If this input isn't mine, ignore it */ @@ -3482,7 +3584,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() // group change with input addresses if (any_mine) { - for (const CTxOut& txout : pcoin->tx->vout) + for (const CTxOut& txout : wtx.tx->vout) if (IsChange(txout)) { CTxDestination txoutAddr; @@ -3499,7 +3601,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() } // group lone addrs by themselves - for (const auto& txout : pcoin->tx->vout) + for (const auto& txout : wtx.tx->vout) if (IsMine(txout)) { CTxDestination address; @@ -3563,6 +3665,10 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal) { + if (!pwallet->CanGetAddresses(internal)) { + return false; + } + if (nIndex == -1) { CKeyPool keypool; @@ -3618,38 +3724,27 @@ void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) } } -void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script) -{ - std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this); - CPubKey pubkey; - if (!rKey->GetReservedKey(pubkey)) - return; - - script = rKey; - script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; -} - void CWallet::LockCoin(const COutPoint& output) { - AssertLockHeld(cs_wallet); // setLockedCoins + AssertLockHeld(cs_wallet); setLockedCoins.insert(output); } void CWallet::UnlockCoin(const COutPoint& output) { - AssertLockHeld(cs_wallet); // setLockedCoins + AssertLockHeld(cs_wallet); setLockedCoins.erase(output); } void CWallet::UnlockAllCoins() { - AssertLockHeld(cs_wallet); // setLockedCoins + AssertLockHeld(cs_wallet); setLockedCoins.clear(); } bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const { - AssertLockHeld(cs_wallet); // setLockedCoins + AssertLockHeld(cs_wallet); COutPoint outpt(hash, n); return (setLockedCoins.count(outpt) > 0); @@ -3657,7 +3752,7 @@ bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const { - AssertLockHeld(cs_wallet); // setLockedCoins + AssertLockHeld(cs_wallet); for (std::set<COutPoint>::iterator it = setLockedCoins.begin(); it != setLockedCoins.end(); it++) { COutPoint outpt = (*it); @@ -3667,8 +3762,8 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t> &mapKeyBirth) const { - AssertLockHeld(cs_wallet); // mapKeyMetadata +void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CTxDestination, int64_t>& mapKeyBirth) const { + AssertLockHeld(cs_wallet); mapKeyBirth.clear(); // get birth times for keys with metadata @@ -3679,11 +3774,12 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C } // map in which we'll infer heights of other keys - CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganized; use a 144-block safety margin - std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock; + 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()) { if (mapKeyBirth.count(keyid) == 0) - mapKeyFirstBlock[keyid] = pindexMax; + mapKeyFirstBlock[keyid] = max_height; } // if there are no such keys, we're done @@ -3694,17 +3790,15 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C for (const auto& entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; - CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock); - if (pindex && chainActive.Contains(pindex)) { + if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) { // ... which are already in a block - int nHeight = pindex->nHeight; for (const CTxOut &txout : wtx.tx->vout) { // iterate over all their outputs for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { // ... and all their affected keys - std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid); - if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight) - rit->second = pindex; + std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid); + if (rit != mapKeyFirstBlock.end() && *height < rit->second) + rit->second = *height; } } } @@ -3712,7 +3806,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C // Extract block timestamps for those keys for (const auto& entry : mapKeyFirstBlock) - mapKeyBirth[entry.first] = entry.second->GetBlockTime() - TIMESTAMP_WINDOW; // block times can be 2h off + mapKeyBirth[entry.first] = locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; // block times can be 2h off } /** @@ -3740,7 +3834,8 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.hashUnset()) { - if (const CBlockIndex* pindex = LookupBlockIndex(wtx.hashBlock)) { + int64_t blocktime; + if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; @@ -3766,7 +3861,6 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const } } - int64_t blocktime = pindex->GetBlockTime(); nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString()); @@ -3814,7 +3908,6 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const { - LOCK(cs_wallet); std::vector<std::string> values; for (const auto& address : mapAddressBook) { for (const auto& data : address.second.destdata) { @@ -3872,6 +3965,9 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b return false; } + // Keep same database environment instance across Verify/Recover calls below. + std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path); + try { if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) { return false; @@ -3883,7 +3979,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b if (salvage_wallet) { // Recover readable keypairs: - CWallet dummyWallet(chain, WalletLocation(), WalletDatabase::CreateDummy()); + CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; @@ -3895,58 +3991,58 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags) { - const std::string& walletFile = location.GetName(); + const std::string& walletFile = WalletDataFilePath(location.GetPath()).string(); // needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; if (gArgs.GetBoolArg("-zapwallettxes", false)) { - uiInterface.InitMessage(_("Zapping all transactions from wallet...")); + chain.initMessage(_("Zapping all transactions from wallet...")); - std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(chain, location, WalletDatabase::Create(location.GetPath())); + std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { - InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } } - uiInterface.InitMessage(_("Loading wallet...")); + chain.initMessage(_("Loading wallet...")); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. - std::shared_ptr<CWallet> walletInstance(new CWallet(chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); + std::shared_ptr<CWallet> walletInstance(new CWallet(&chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { - InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile)); return nullptr; } else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { - InitWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data" - " or address book entries might be missing or incorrect."), + chain.initWarning(strprintf(_("Error reading %s! All keys read correctly, but transaction data" + " or address book entries might be missing or incorrect."), walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { - InitError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); + chain.initError(strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, _(PACKAGE_NAME))); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { - InitError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); + chain.initError(strprintf(_("Wallet needed to be rewritten: restart %s to complete"), _(PACKAGE_NAME))); return nullptr; } else { - InitError(strprintf(_("Error loading %s"), walletFile)); + chain.initError(strprintf(_("Error loading %s"), walletFile)); return nullptr; } } - int prev_version = walletInstance->nWalletVersion; + int prev_version = walletInstance->GetVersion(); if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) { int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); @@ -3960,7 +4056,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); if (nMaxVersion < walletInstance->GetVersion()) { - InitError(_("Cannot downgrade wallet")); + chain.initError(_("Cannot downgrade wallet")); return nullptr; } walletInstance->SetMaxVersion(nMaxVersion); @@ -3971,9 +4067,9 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, LOCK(walletInstance->cs_wallet); // Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT - int max_version = walletInstance->nWalletVersion; - if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >=FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { - InitError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.")); + int max_version = walletInstance->GetVersion(); + if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { + chain.initError(_("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use -upgradewallet=169900 or -upgradewallet with no version specified.")); return nullptr; } @@ -4001,7 +4097,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Regenerate the keypool if upgraded to HD if (hd_upgrade) { if (!walletInstance->TopUpKeyPool()) { - InitError(_("Unable to generate keys")); + chain.initError(_("Unable to generate keys")); return nullptr; } } @@ -4015,50 +4111,52 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { //selective allow to set flags walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) { + walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET); } else { // generate a new seed CPubKey seed = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(seed); - } + } // Otherwise, do not generate a new seed // Top up the keypool - if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) { - InitError(_("Unable to generate initial keys")); + if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { + chain.initError(_("Unable to generate initial keys")); return nullptr; } auto locked_chain = chain.assumeLocked(); // Temporary. Removed in upcoming lock cleanup - walletInstance->ChainStateFlushed(chainActive.GetLocator()); + walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation - InitError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile)); + chain.initError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile)); return NULL; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { LOCK(walletInstance->cs_KeyStore); if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { - InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); + chain.initWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); } } if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) { - InitError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); + chain.initError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", ""))); return nullptr; } if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) { - InitError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); + chain.initError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", ""))); return nullptr; } if (gArgs.IsArgSet("-mintxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) { - InitError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); + chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { - InitWarning(AmountHighWarn("-mintxfee") + " " + - _("This is the minimum transaction fee you pay on every transaction.")); + chain.initWarning(AmountHighWarn("-mintxfee") + " " + + _("This is the minimum transaction fee you pay on every transaction.")); } walletInstance->m_min_fee = CFeeRate(n); } @@ -4067,12 +4165,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-fallbackfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { - InitError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""))); + chain.initError(strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - InitWarning(AmountHighWarn("-fallbackfee") + " " + - _("This is the transaction fee you may pay when fee estimates are not available.")); + chain.initWarning(AmountHighWarn("-fallbackfee") + " " + + _("This is the transaction fee you may pay when fee estimates are not available.")); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); walletInstance->m_allow_fallback_fee = nFeePerK != 0; //disable fallback fee in case value was set to 0, enable if non-null value @@ -4080,29 +4178,29 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-discardfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) { - InitError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""))); + chain.initError(strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - InitWarning(AmountHighWarn("-discardfee") + " " + - _("This is the transaction fee you may discard if change is smaller than dust at this level")); + chain.initWarning(AmountHighWarn("-discardfee") + " " + + _("This is the transaction fee you may discard if change is smaller than dust at this level")); } walletInstance->m_discard_rate = CFeeRate(nFeePerK); } if (gArgs.IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { - InitError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); + chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - InitWarning(AmountHighWarn("-paytxfee") + " " + - _("This is the transaction fee you will pay if you send a transaction.")); + chain.initWarning(AmountHighWarn("-paytxfee") + " " + + _("This is the transaction fee you will pay if you send a transaction.")); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); - if (walletInstance->m_pay_tx_fee < ::minRelayTxFee) { - InitError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), - gArgs.GetArg("-paytxfee", ""), ::minRelayTxFee.ToString())); + if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) { + chain.initError(strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), + gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString())); return nullptr; } } @@ -4115,58 +4213,66 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - LockAnnotation lock(::cs_main); // Temporary, for FindForkInGlobalIndex below. Removed in upcoming commit. auto locked_chain = chain.lock(); LOCK(walletInstance->cs_wallet); - CBlockIndex *pindexRescan = chainActive.Genesis(); + int rescan_height = 0; if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; - if (batch.ReadBestBlock(locator)) - pindexRescan = FindForkInGlobalIndex(chainActive, locator); + if (batch.ReadBestBlock(locator)) { + if (const Optional<int> fork_height = locked_chain->findLocatorFork(locator)) { + rescan_height = *fork_height; + } + } } - walletInstance->m_last_block_processed = chainActive.Tip(); + const Optional<int> tip_height = locked_chain->getHeight(); + if (tip_height) { + walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); + } else { + walletInstance->m_last_block_processed.SetNull(); + } - if (chainActive.Tip() && chainActive.Tip() != pindexRescan) + if (tip_height && *tip_height != rescan_height) { //We can't rescan beyond non-pruned blocks, stop and throw an error //this might happen if a user uses an old wallet within a pruned node // or if he ran -disablewallet for a longer time, then decided to re-enable - if (fPruneMode) - { - CBlockIndex *block = chainActive.Tip(); - while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA) && block->pprev->nTx > 0 && pindexRescan != block) - block = block->pprev; + if (chain.getPruneMode()) { + int block_height = *tip_height; + while (block_height > 0 && locked_chain->haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { + --block_height; + } - if (pindexRescan != block) { - InitError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); + if (rescan_height != block_height) { + chain.initError(_("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)")); return nullptr; } } - uiInterface.InitMessage(_("Rescanning...")); - walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); + chain.initMessage(_("Rescanning...")); + walletInstance->WalletLogPrintf("Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height); // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) - while (pindexRescan && walletInstance->nTimeFirstKey && (pindexRescan->GetBlockTime() < (walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) { - pindexRescan = chainActive.Next(pindexRescan); + if (walletInstance->nTimeFirstKey) { + if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height)) { + rescan_height = *first_block; + } } nStart = GetTimeMillis(); { WalletRescanReserver reserver(walletInstance.get()); - const CBlockIndex *stop_block, *failed_block; - if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, reserver, failed_block, stop_block, true))) { - InitError(_("Failed to rescan the wallet during initialization")); + if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), {} /* stop block */, reserver, true /* update */).status)) { + chain.initError(_("Failed to rescan the wallet during initialization")); return nullptr; } } walletInstance->WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - nStart); - walletInstance->ChainStateFlushed(chainActive.GetLocator()); + walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 @@ -4194,10 +4300,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } } - uiInterface.LoadWallet(walletInstance); + chain.loadWallet(interfaces::MakeWallet(walletInstance)); - // Register with the validation interface. It's ok to do this after rescan since we're still holding cs_main. - RegisterValidationInterface(walletInstance.get()); + // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. + walletInstance->handleNotifications(); walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); @@ -4210,11 +4316,22 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return walletInstance; } +void CWallet::handleNotifications() +{ + m_chain_notifications_handler = m_chain->handleNotifications(*this); +} + void CWallet::postInitProcess() { + auto locked_chain = chain().lock(); + LOCK(cs_wallet); + // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded - ReacceptWalletTransactions(); + ReacceptWalletTransactions(*locked_chain); + + // Update wallet transactions with current mempool transactions. + chain().requestMempoolTransactions(*this); } bool CWallet::BackupWallet(const std::string& strDest) @@ -4243,10 +4360,10 @@ CWalletKey::CWalletKey(int64_t nExpires) nTimeExpires = nExpires; } -void CMerkleTx::SetMerkleBranch(const CBlockIndex* pindex, int posInBlock) +void CMerkleTx::SetMerkleBranch(const uint256& block_hash, int posInBlock) { // Update the tx's hashBlock - hashBlock = pindex->GetBlockHash(); + hashBlock = block_hash; // set the position of the transaction in the block nIndex = posInBlock; @@ -4257,14 +4374,7 @@ int CMerkleTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const if (hashUnset()) return 0; - AssertLockHeld(cs_main); - - // Find the block it claims to be in - CBlockIndex* pindex = LookupBlockIndex(hashBlock); - if (!pindex || !chainActive.Contains(pindex)) - return 0; - - return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1); + return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); } int CMerkleTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const @@ -4282,17 +4392,14 @@ bool CMerkleTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const return GetBlocksToMaturity(locked_chain) > 0; } -bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state) +bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state) { - LockAnnotation lock(::cs_main); // Temporary, for AcceptToMemoryPool below. Removed in upcoming commit. - // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors // because we think that this newly generated transaction's change is // unavailable as we're not yet aware that it is in the mempool. - bool ret = ::AcceptToMemoryPool(mempool, state, tx, nullptr /* pfMissingInputs */, - nullptr /* plTxnReplaced */, false /* bypass_limits */, nAbsurdFee); + bool ret = locked_chain.submitToMemoryPool(tx, pwallet->chain().maxTxFee(), state); fInMempool |= ret; return ret; } @@ -4323,7 +4430,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu CInputCoin input_coin = output.GetInputCoin(); size_t ancestors, descendants; - mempool.GetTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) { // Limit output groups to no more than 10 entries, to protect // against inadvertently creating a too-large transaction @@ -4354,18 +4461,21 @@ bool CWallet::GetKeyOrigin(const CKeyID& keyID, KeyOriginInfo& info) const meta = it->second; } } - if (!meta.hdKeypath.empty()) { - if (!ParseHDKeypath(meta.hdKeypath, info.path)) return false; - // Get the proper master key id - CKey key; - GetKey(meta.hd_seed_id, key); - CExtKey masterKey; - masterKey.SetSeed(key.begin(), key.size()); - // Compute identifier - CKeyID masterid = masterKey.key.GetPubKey().GetID(); - std::copy(masterid.begin(), masterid.begin() + 4, info.fingerprint); + 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::AddKeyOrigin(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 WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3a3ec43c9b..62ba0aa962 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -8,18 +8,19 @@ #include <amount.h> #include <interfaces/chain.h> +#include <interfaces/handler.h> #include <outputtype.h> #include <policy/feerate.h> +#include <script/ismine.h> +#include <script/sign.h> #include <streams.h> #include <tinyformat.h> #include <ui_interface.h> #include <util/strencodings.h> -#include <validationinterface.h> -#include <script/ismine.h> -#include <script/sign.h> #include <util/system.h> -#include <wallet/crypter.h> +#include <validationinterface.h> #include <wallet/coinselection.h> +#include <wallet/crypter.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -34,31 +35,19 @@ #include <utility> #include <vector> -//! Responsible for reading and validating the -wallet arguments and verifying the wallet database. -// This function will perform salvage on the wallet if requested, as long as only one wallet is -// being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). -bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); - -//! Load wallet databases. -bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files); - -//! Complete startup of wallets. -void StartWallets(CScheduler& scheduler); - -//! Flush all wallets in preparation for shutdown. -void FlushWallets(); - -//! Stop all wallets. Wallets will be flushed first. -void StopWallets(); - -//! Close all wallets. -void UnloadWallets(); +//! Explicitly unload and delete the wallet. +//! Blocks the current thread after signaling the unload intent so that all +//! wallet clients release the wallet. +//! Note that, when blocking is not required, the wallet is implicitly unloaded +//! by the shared pointer deleter. +void UnloadWallet(std::shared_ptr<CWallet>&& wallet); bool AddWallet(const std::shared_ptr<CWallet>& wallet); bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::string& warning); //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; @@ -88,13 +77,10 @@ static const bool DEFAULT_DISABLE_WALLET = false; //! Pre-calculated constants for input size estimation in *virtual size* static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91; -class CBlockIndex; class CCoinControl; class COutput; class CReserveKey; class CScript; -class CTxMemPool; -class CBlockPolicyEstimator; class CWalletTx; struct FeeCalculation; enum class FeeEstimateMode; @@ -128,11 +114,26 @@ 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 + // 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 g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS; +static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA; /** A key pool entry */ class CKeyPool @@ -280,7 +281,7 @@ public: READWRITE(nIndex); } - void SetMerkleBranch(const CBlockIndex* pIndex, int posInBlock); + void SetMerkleBranch(const uint256& block_hash, int posInBlock); /** * Return depth of transaction in blockchain: @@ -485,7 +486,7 @@ public: CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(cs_main, pwallet->cs_wallet)". The + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type CWallet. CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; @@ -514,11 +515,11 @@ public: int64_t GetTxTime() const; - // RelayWalletTransaction may only be called if fBroadcastTransactions! - bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain, CConnman* connman); + // Pass this transaction to the node to relay to its peers + bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain); /** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */ - bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, const CAmount& nAbsurdFee, CValidationState& state); + bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -581,8 +582,8 @@ public: int64_t nTimeCreated; int64_t nTimeExpires; std::string strComment; - //! todo: add something to note what created it (user, getnewaddress, change) - //! maybe should have a map<string, string> property map + // todo: add something to note what created it (user, getnewaddress, change) + // maybe should have a map<string, string> property map explicit CWalletKey(int64_t nExpires=0); @@ -617,7 +618,7 @@ class WalletRescanReserver; //forward declarations for ScanForWalletTransactions * 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. */ -class CWallet final : public CCryptoKeyStore, public CValidationInterface +class CWallet final : public CCryptoKeyStore, private interfaces::Chain::Notifications { private: std::atomic<bool> fAbortRescan{false}; @@ -628,7 +629,7 @@ private: 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 = FEATURE_BASE; + int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE}; //! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; @@ -636,6 +637,8 @@ private: int64_t nNextResend = 0; int64_t nLastResend = 0; bool fBroadcastTransactions = false; + // Local time that the tip block was received. Used to schedule wallet rebroadcasts. + std::atomic<int64_t> m_best_block_time {0}; /** * Used to keep track of spent outpoints, and @@ -660,7 +663,7 @@ private: * Abandoned state should probably be more carefully tracked via different * posInBlock signals or by checking mempool presence when necessary. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); @@ -671,18 +674,18 @@ private: void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. - * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */ - void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = nullptr, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + * 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, 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); + void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::set<int64_t> setInternalKeyPool; + 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; + 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}; @@ -701,7 +704,7 @@ private: bool AddWatchOnly(const CScript& dest) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** Interface for accessing chain state. */ - interfaces::Chain& m_chain; + interfaces::Chain* m_chain; /** Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; @@ -716,10 +719,8 @@ private: * Note that this is *not* how far we've processed, we may need some rescan * to have seen all transactions in the chain, but is only used to track * live BlockConnected callbacks. - * - * Protected by cs_main (see BlockUntilSyncedToCurrentChain) */ - const CBlockIndex* m_last_block_processed = nullptr; + uint256 m_last_block_processed; public: /* @@ -759,17 +760,24 @@ public: // Map from Script ID to key metadata (for watch-only keys). std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); + bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, bool overwrite); + typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ - CWallet(interfaces::Chain& chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) : m_chain(chain), m_location(location), database(std::move(database)) + CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) + : m_chain(chain), + m_location(location), + database(std::move(database)) { } ~CWallet() { + // Should not have slots connected at this point. + assert(NotifyUnload.empty()); delete encrypted_batch; encrypted_batch = nullptr; } @@ -782,12 +790,18 @@ public: int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; uint64_t nAccountingEntryNumber = 0; - std::map<CTxDestination, CAddressBookData> mapAddressBook; + std::map<CTxDestination, CAddressBookData> mapAddressBook GUARDED_BY(cs_wallet); std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); + /** Registered interfaces::Chain::Notifications handler. */ + std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; + + /** Register the wallet for chain notifications */ + void handleNotifications(); + /** Interface for accessing chain state. */ - interfaces::Chain& chain() const { return m_chain; } + interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -847,6 +861,8 @@ public: //! 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); @@ -859,15 +875,15 @@ public: 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); + bool AddDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Erases a destination data tuple in the store and on disk - bool EraseDestData(const CTxDestination &dest, const std::string &key); + bool EraseDestData(const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a destination data tuple to the store, without saving it to disk - void LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value); + void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Look up a destination data tuple in the store, return true if found false otherwise - bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; + bool GetDestData(const CTxDestination& dest, const std::string& key, std::string* value) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Get all destination values matching a prefix. - std::vector<std::string> GetDestValues(const std::string& prefix) const; + 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); @@ -878,7 +894,7 @@ public: //! 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; - bool Unlock(const SecureString& strWalletPassphrase); + bool Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys = false); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); @@ -896,27 +912,39 @@ public: bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; - void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override; - void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; + void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; + void BlockDisconnected(const CBlock& block) override; + void UpdatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); - enum class ScanResult { - SUCCESS, - FAILURE, - USER_ABORT + struct ScanResult { + enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS; + + //! Hash and height of most recent block that was successfully scanned. + //! Unset if no blocks were scanned due to read errors or the chain + //! being empty. + uint256 last_scanned_block; + Optional<int> last_scanned_height; + + //! Height of the most recent block that could not be scanned due to + //! read errors or pruning. Will be set if status is FAILURE, unset if + //! status is SUCCESS, and may or may not be set if status is + //! USER_ABORT. + uint256 last_failed_block; }; - ScanResult ScanForWalletTransactions(const CBlockIndex* const pindexStart, const CBlockIndex* const pindexStop, const WalletRescanReserver& reserver, const CBlockIndex*& failed_block, const CBlockIndex*& stop_block, bool fUpdate = false); + ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; - void ReacceptWalletTransactions(); - void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override EXCLUSIVE_LOCKS_REQUIRED(cs_main); - // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! - std::vector<uint256> ResendWalletTransactionsBefore(interfaces::Chain::Lock& locked_chain, int64_t nTime, CConnman* connman); - CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const; - CAmount GetUnconfirmedBalance() const; - CAmount GetImmatureBalance() const; - CAmount GetUnconfirmedWatchOnlyBalance() const; - CAmount GetImmatureWatchOnlyBalance() const; - CAmount GetLegacyBalance(const isminefilter& filter, int minDepth) const; + void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void ResendWalletTransactions(); + struct Balance { + CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more + CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending) + CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain + CAmount m_watchonly_trusted{0}; + CAmount m_watchonly_untrusted_pending{0}; + CAmount m_watchonly_immature{0}; + }; + Balance GetBalance(int min_depth = 0) const; CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const; OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient>& vecSend); @@ -935,7 +963,7 @@ public: */ bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); - bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CConnman* connman, CValidationState& state); + bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, CReserveKey& reservekey, CValidationState& state); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { @@ -965,6 +993,8 @@ public: bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); + void AddKeypoolPubkey(const CPubKey& pubkey, const bool internal); + void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); /** * Reserves a key from the keypool and sets nIndex to its index @@ -1025,13 +1055,11 @@ public: bool DelAddressBook(const CTxDestination& address); - const std::string& GetLabelName(const CScript& scriptPubKey) const; - - void GetScriptForMining(std::shared_ptr<CReserveScript> &script); + const std::string& GetLabelName(const CScript& scriptPubKey) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { - AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool + AssertLockHeld(cs_wallet); return setInternalKeyPool.size() + setExternalKeyPool.size(); } @@ -1078,6 +1106,9 @@ public: /** Watch-only address added */ boost::signals2::signal<void (bool fHaveWatchOnly)> NotifyWatchonlyChanged; + /** Keypool has new keys */ + boost::signals2::signal<void ()> NotifyCanGetAddressesChanged; + /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ @@ -1113,6 +1144,12 @@ public: /* 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(); @@ -1150,6 +1187,9 @@ public: /** set a single wallet flag */ void SetWalletFlag(uint64_t flags); + /** Unsets a single wallet flag */ + void UnsetWalletFlag(uint64_t flag); + /** check if a certain wallet flag is set */ bool IsWalletFlagSet(uint64_t flag); @@ -1171,10 +1211,19 @@ public: /** Implement lookup of key origin information through wallet key metadata. */ bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + + /** Add a KeyOriginInfo to the wallet */ + bool AddKeyOrigin(const CPubKey& pubkey, const KeyOriginInfo& info); }; +/** + * Called periodically by the schedule thread. Prompts individual wallets to resend + * their transactions. Actual rebroadcast schedule is managed by the wallets themselves. + */ +void MaybeResendWalletTxs(); + /** A key allocated from the key pool. */ -class CReserveKey final : public CReserveScript +class CReserveKey { protected: CWallet* pwallet; @@ -1199,7 +1248,6 @@ public: void ReturnKey(); bool GetReservedKey(CPubKey &pubkey, bool internal = false); void KeepKey(); - void KeepScript() override { KeepKey(); } }; /** RAII object to check and reserve a wallet rescan */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 6e037808e3..3122cd6fa4 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -5,7 +5,7 @@ #include <wallet/walletdb.h> -#include <consensus/tx_verify.h> +#include <consensus/tx_check.h> #include <consensus/validation.h> #include <fs.h> #include <key_io.h> @@ -57,9 +57,14 @@ bool WalletBatch::EraseTx(uint256 hash) return EraseIC(std::make_pair(std::string("tx"), hash)); } +bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite) +{ + return WriteIC(std::make_pair(std::string("keymeta"), pubkey), meta, overwrite); +} + bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) { - if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) { + if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) { return false; } @@ -76,7 +81,7 @@ bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta) { - if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) { + if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) { return false; } @@ -417,8 +422,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strType != "minversion" && strType != "acentry") { wss.m_unknown_records++; } - } catch (...) - { + } catch (const std::exception& e) { + if (strErr.empty()) { + strErr = e.what(); + } + return false; + } catch (...) { + if (strErr.empty()) { + strErr = "Caught unknown exception in ReadKeyValue"; + } return false; } return true; @@ -529,6 +541,14 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) if (wss.fAnyUnordered) result = pwallet->ReorderTransactions(); + // Upgrade all of the wallet keymetadata to have the hd master key id + // This operation is not atomic, but if it fails, updated entries are still backwards compatible with older software + try { + pwallet->UpgradeKeyMetadata(); + } catch (...) { + result = DBErrors::CORRUPT; + } + return result; } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 5584407a56..0532a55ff5 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -8,6 +8,7 @@ #include <amount.h> #include <primitives/transaction.h> +#include <script/sign.h> #include <wallet/db.h> #include <key.h> @@ -93,11 +94,14 @@ class CKeyMetadata public: static const int VERSION_BASIC=1; static const int VERSION_WITH_HDDATA=10; - static const int CURRENT_VERSION=VERSION_WITH_HDDATA; + static const int VERSION_WITH_KEY_ORIGIN = 12; + static const int CURRENT_VERSION=VERSION_WITH_KEY_ORIGIN; int nVersion; int64_t nCreateTime; // 0 means unknown - std::string hdKeypath; //optional HD/bip32 keypath + std::string hdKeypath; //optional HD/bip32 keypath. Still used to determine whether a key is a seed. Also kept for backwards compatibility CKeyID hd_seed_id; //id of the HD seed used to derive this key + KeyOriginInfo key_origin; // Key origin info with path and fingerprint + bool has_key_origin = false; //< Whether the key_origin is useful CKeyMetadata() { @@ -120,6 +124,11 @@ public: READWRITE(hdKeypath); READWRITE(hd_seed_id); } + if (this->nVersion >= VERSION_WITH_KEY_ORIGIN) + { + READWRITE(key_origin); + READWRITE(has_key_origin); + } } void SetNull() @@ -128,6 +137,8 @@ public: nCreateTime = 0; hdKeypath.clear(); hd_seed_id.SetNull(); + key_origin.clear(); + has_key_origin = false; } }; @@ -177,6 +188,7 @@ public: bool WriteTx(const CWalletTx& wtx); bool EraseTx(uint256 hash); + bool WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite); bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata &keyMeta); bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta); bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey); diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp new file mode 100644 index 0000000000..1ff1e8b840 --- /dev/null +++ b/src/wallet/wallettool.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2016-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 <base58.h> +#include <fs.h> +#include <util/system.h> +#include <wallet/wallet.h> +#include <wallet/walletutil.h> + +namespace WalletTool { + +// The standard wallet deleter function blocks on the validation interface +// queue, which doesn't exist for the bitcoin-wallet. Define our own +// deleter here. +static void WalletToolReleaseWallet(CWallet* wallet) +{ + wallet->WalletLogPrintf("Releasing wallet\n"); + wallet->Flush(true); + delete wallet; +} + +static std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path) +{ + if (fs::exists(path)) { + fprintf(stderr, "Error: File exists already\n"); + return nullptr; + } + // dummy chain interface + std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); + bool first_run = true; + DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run); + if (load_wallet_ret != DBErrors::LOAD_OK) { + fprintf(stderr, "Error creating %s", name.c_str()); + return nullptr; + } + + wallet_instance->SetMinVersion(FEATURE_HD_SPLIT); + + // generate a new HD seed + CPubKey seed = wallet_instance->GenerateNewSeed(); + wallet_instance->SetHDSeed(seed); + + fprintf(stdout, "Topping up keypool...\n"); + wallet_instance->TopUpKeyPool(); + return wallet_instance; +} + +static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path) +{ + if (!fs::exists(path)) { + fprintf(stderr, "Error: Wallet files does not exist\n"); + return nullptr; + } + + // dummy chain interface + std::shared_ptr<CWallet> wallet_instance(new CWallet(nullptr /* chain */, WalletLocation(name), WalletDatabase::Create(path)), WalletToolReleaseWallet); + DBErrors load_wallet_ret; + try { + bool first_run; + load_wallet_ret = wallet_instance->LoadWallet(first_run); + } catch (const std::runtime_error) { + fprintf(stderr, "Error loading %s. Is wallet being used by another process?\n", name.c_str()); + return nullptr; + } + + if (load_wallet_ret != DBErrors::LOAD_OK) { + wallet_instance = nullptr; + if (load_wallet_ret == DBErrors::CORRUPT) { + fprintf(stderr, "Error loading %s: Wallet corrupted", name.c_str()); + return nullptr; + } else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) { + fprintf(stderr, "Error reading %s! All keys read correctly, but transaction data" + " or address book entries might be missing or incorrect.", + name.c_str()); + } else if (load_wallet_ret == DBErrors::TOO_NEW) { + fprintf(stderr, "Error loading %s: Wallet requires newer version of %s", + name.c_str(), PACKAGE_NAME); + return nullptr; + } else if (load_wallet_ret == DBErrors::NEED_REWRITE) { + fprintf(stderr, "Wallet needed to be rewritten: restart %s to complete", PACKAGE_NAME); + return nullptr; + } else { + fprintf(stderr, "Error loading %s", name.c_str()); + return nullptr; + } + } + + return wallet_instance; +} + +static void WalletShowInfo(CWallet* wallet_instance) +{ + LOCK(wallet_instance->cs_wallet); + + fprintf(stdout, "Wallet info\n===========\n"); + fprintf(stdout, "Encrypted: %s\n", wallet_instance->IsCrypted() ? "yes" : "no"); + fprintf(stdout, "HD (hd seed available): %s\n", wallet_instance->GetHDChain().seed_id.IsNull() ? "no" : "yes"); + fprintf(stdout, "Keypool Size: %u\n", wallet_instance->GetKeyPoolSize()); + fprintf(stdout, "Transactions: %zu\n", wallet_instance->mapWallet.size()); + fprintf(stdout, "Address Book: %zu\n", wallet_instance->mapAddressBook.size()); +} + +bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) +{ + fs::path path = fs::absolute(name, GetWalletDir()); + + if (command == "create") { + std::shared_ptr<CWallet> wallet_instance = CreateWallet(name, path); + if (wallet_instance) { + WalletShowInfo(wallet_instance.get()); + wallet_instance->Flush(true); + } + } else if (command == "info") { + if (!fs::exists(path)) { + fprintf(stderr, "Error: no wallet file at %s\n", name.c_str()); + return false; + } + std::string error; + if (!WalletBatch::VerifyEnvironment(path, error)) { + fprintf(stderr, "Error loading %s. Is wallet being used by other process?\n", name.c_str()); + return false; + } + std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path); + if (!wallet_instance) return false; + WalletShowInfo(wallet_instance.get()); + wallet_instance->Flush(true); + } else { + fprintf(stderr, "Invalid command: %s\n", command.c_str()); + return false; + } + + return true; +} +} // namespace WalletTool diff --git a/src/wallet/wallettool.h b/src/wallet/wallettool.h new file mode 100644 index 0000000000..5b06fd1792 --- /dev/null +++ b/src/wallet/wallettool.h @@ -0,0 +1,20 @@ +// Copyright (c) 2016-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. + +#ifndef BITCOIN_WALLET_WALLETTOOL_H +#define BITCOIN_WALLET_WALLETTOOL_H + +#include <script/ismine.h> +#include <wallet/wallet.h> + +namespace WalletTool { + +std::shared_ptr<CWallet> CreateWallet(const std::string& name, const fs::path& path); +std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::path& path); +void WalletShowInfo(CWallet* wallet_instance); +bool ExecuteWalletToolFunc(const std::string& command, const std::string& file); + +} // namespace WalletTool + +#endif // BITCOIN_WALLET_WALLETTOOL_H diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp index 6db4c63acb..2d8adf8ba6 100644 --- a/src/wallet/walletutil.cpp +++ b/src/wallet/walletutil.cpp @@ -4,6 +4,7 @@ #include <wallet/walletutil.h> +#include <logging.h> #include <util/system.h> fs::path GetWalletDir() @@ -33,9 +34,11 @@ static bool IsBerkeleyBtree(const fs::path& path) // A Berkeley DB Btree file has at least 4K. // This check also prevents opening lock files. boost::system::error_code ec; - if (fs::file_size(path, ec) < 4096) return false; + auto size = fs::file_size(path, ec); + if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string()); + if (size < 4096) return false; - fs::ifstream file(path.string(), std::ios::binary); + fsbridge::ifstream file(path, std::ios::binary); if (!file.is_open()) return false; file.seekg(12, std::ios::beg); // Magic bytes start at offset 12 @@ -54,8 +57,14 @@ std::vector<fs::path> ListWalletDir() const fs::path wallet_dir = GetWalletDir(); const size_t offset = wallet_dir.string().size() + 1; std::vector<fs::path> paths; + boost::system::error_code ec; + + for (auto it = fs::recursive_directory_iterator(wallet_dir, ec); it != fs::recursive_directory_iterator(); it.increment(ec)) { + if (ec) { + LogPrintf("%s: %s %s\n", __func__, ec.message(), it->path().string()); + continue; + } - for (auto it = fs::recursive_directory_iterator(wallet_dir); it != fs::recursive_directory_iterator(); ++it) { // Get wallet path relative to walletdir by removing walletdir from the wallet path. // This can be replaced by boost::filesystem::lexically_relative once boost is bumped to 1.60. const fs::path path = it->path().string().substr(offset); |