diff --git a/doc/release-notes.md b/doc/release-notes.md index c7aa95d60..be871b8c1 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,17 +1,38 @@ # Bitcoin ABC 0.24.3 Release Notes Bitcoin ABC version 0.24.3 is now available from: This release includes the following features and fixes: - - The `-zapwallettxes` startup option has been removed and its functionality removed - from the wallet. This functionality has been superseded with the abandon transaction - feature. - - `getnetworkinfo` now returns two new fields, `connections_in` and - `connections_out`, that provide the number of inbound and outbound peer - connections. These new fields are in addition to the existing `connections` - field, which returns the total number of peer connections. - - The `connections` field of `bitcoin-cli -getinfo` is expanded to return a JSON - object with `in`, `out` and `total` numbers of peer connections. It previously - returned a single integer value for the total number of peer connections. + +Wallet +------ + +- The `-zapwallettxes` startup option has been removed and its functionality removed + from the wallet. This functionality has been superseded with the abandon transaction + feature. + +Configuration +------------- + +The `createwallet`, `loadwallet`, and `unloadwallet` RPCs now accept +`load_on_startup` options that modify bitcoin's dynamic configuration in +`\/settings.json`, and can add or remove a wallet from the list of +wallets automatically loaded at startup. Unless these options are explicitly +set to true or false, the load on startup wallet list is not modified, so this +change is backwards compatible. + +In the future, the GUI will start updating the same startup wallet list as the +RPCs to automatically reopen wallets previously opened in the GUI. + +RPCs +---- + +- `getnetworkinfo` now returns two new fields, `connections_in` and + `connections_out`, that provide the number of inbound and outbound peer + connections. These new fields are in addition to the existing `connections` + field, which returns the total number of peer connections. +- The `connections` field of `bitcoin-cli -getinfo` is expanded to return a JSON + object with `in`, `out` and `total` numbers of peer connections. It previously + returned a single integer value for the total number of peer connections. diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index e2409277d..ab3298557 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -1,472 +1,493 @@ // Copyright (c) 2018-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace interfaces { namespace { bool FillBlock(const CBlockIndex *index, const FoundBlock &block, UniqueLock &lock) { if (!index) { return false; } if (block.m_hash) { *block.m_hash = index->GetBlockHash(); } if (block.m_height) { *block.m_height = index->nHeight; } if (block.m_time) { *block.m_time = index->GetBlockTime(); } if (block.m_max_time) { *block.m_max_time = index->GetBlockTimeMax(); } if (block.m_mtp_time) { *block.m_mtp_time = index->GetMedianTimePast(); } if (block.m_data) { REVERSE_LOCK(lock); if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) { block.m_data->SetNull(); } } return true; } class NotificationsProxy : public CValidationInterface { public: explicit NotificationsProxy( std::shared_ptr notifications) : m_notifications(std::move(notifications)) {} virtual ~NotificationsProxy() = default; void TransactionAddedToMempool(const CTransactionRef &tx) override { m_notifications->transactionAddedToMempool(tx); } void TransactionRemovedFromMempool(const CTransactionRef &tx, MemPoolRemovalReason reason) override { m_notifications->transactionRemovedFromMempool(tx, reason); } void BlockConnected(const std::shared_ptr &block, const CBlockIndex *index) override { m_notifications->blockConnected(*block, index->nHeight); } void BlockDisconnected(const std::shared_ptr &block, const CBlockIndex *index) override { m_notifications->blockDisconnected(*block, index->nHeight); } void UpdatedBlockTip(const CBlockIndex *index, const CBlockIndex *fork_index, bool is_ibd) override { m_notifications->updatedBlockTip(); } void ChainStateFlushed(const CBlockLocator &locator) override { m_notifications->chainStateFlushed(locator); } std::shared_ptr m_notifications; }; class NotificationsHandlerImpl : public Handler { public: explicit NotificationsHandlerImpl( std::shared_ptr notifications) : m_proxy(std::make_shared( std::move(notifications))) { RegisterSharedValidationInterface(m_proxy); } ~NotificationsHandlerImpl() override { disconnect(); } void disconnect() override { if (m_proxy) { UnregisterSharedValidationInterface(m_proxy); m_proxy.reset(); } } std::shared_ptr m_proxy; }; class RpcHandlerImpl : public Handler { public: explicit RpcHandlerImpl(const CRPCCommand &command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](const Config &config, const JSONRPCRequest &request, UniValue &result, bool last_handler) { if (!m_wrapped_command) { return false; } try { return m_wrapped_command->actor(config, request, result, last_handler); } catch (const UniValue &e) { // If this is not the last handler and a wallet not found // exception was thrown, return false so the next handler // can try to handle the request. Otherwise, reraise the // exception. if (!last_handler) { const UniValue &code = e["code"]; if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { return false; } } throw; } }; ::tableRPC.appendCommand(m_command.name, &m_command); } void disconnect() final { if (m_wrapped_command) { m_wrapped_command = nullptr; ::tableRPC.removeCommand(m_command.name, &m_command); } } ~RpcHandlerImpl() override { disconnect(); } CRPCCommand m_command; const CRPCCommand *m_wrapped_command; }; class ChainImpl : public Chain { public: explicit ChainImpl(NodeContext &node, const CChainParams ¶ms) : m_node(node), m_params(params) {} std::optional getHeight() override { LOCK(::cs_main); int height = ::ChainActive().Height(); if (height >= 0) { return height; } return std::nullopt; } std::optional getBlockHeight(const BlockHash &hash) override { LOCK(::cs_main); CBlockIndex *block = LookupBlockIndex(hash); if (block && ::ChainActive().Contains(block)) { return block->nHeight; } return std::nullopt; } BlockHash getBlockHash(int height) override { LOCK(::cs_main); CBlockIndex *block = ::ChainActive()[height]; assert(block); return block->GetBlockHash(); } bool haveBlockOnDisk(int height) override { LOCK(cs_main); CBlockIndex *block = ::ChainActive()[height]; return block && (block->nStatus.hasData() != 0) && block->nTx > 0; } std::optional findFirstBlockWithTimeAndHeight(int64_t time, int height, BlockHash *hash) override { LOCK(cs_main); CBlockIndex *block = ::ChainActive().FindEarliestAtLeast(time, height); if (block) { if (hash) { *hash = block->GetBlockHash(); } return block->nHeight; } return std::nullopt; } CBlockLocator getTipLocator() override { LOCK(cs_main); return ::ChainActive().GetLocator(); } bool contextualCheckTransactionForCurrentBlock( const CTransaction &tx, TxValidationState &state) override { LOCK(cs_main); return ContextualCheckTransactionForCurrentBlock( m_params.GetConsensus(), tx, state); } std::optional findLocatorFork(const CBlockLocator &locator) override { LOCK(cs_main); if (CBlockIndex *fork = FindForkInGlobalIndex(::ChainActive(), locator)) { return fork->nHeight; } return std::nullopt; } bool findBlock(const BlockHash &hash, const FoundBlock &block) override { WAIT_LOCK(cs_main, lock); return FillBlock(LookupBlockIndex(hash), block, lock); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock &block) override { WAIT_LOCK(cs_main, lock); return FillBlock( ChainActive().FindEarliestAtLeast(min_time, min_height), block, lock); } bool findNextBlock(const BlockHash &block_hash, int block_height, const FoundBlock &next, bool *reorg) override { WAIT_LOCK(cs_main, lock); CBlockIndex *block = ChainActive()[block_height]; if (block && block->GetBlockHash() != block_hash) { block = nullptr; } if (reorg) { *reorg = !block; } return FillBlock(block ? ChainActive()[block_height + 1] : nullptr, next, lock); } bool findAncestorByHeight(const BlockHash &block_hash, int ancestor_height, const FoundBlock &ancestor_out) override { WAIT_LOCK(cs_main, lock); if (const CBlockIndex *block = LookupBlockIndex(block_hash)) { if (const CBlockIndex *ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock); } } return FillBlock(nullptr, ancestor_out, lock); } bool findAncestorByHash(const BlockHash &block_hash, const BlockHash &ancestor_hash, const FoundBlock &ancestor_out) override { WAIT_LOCK(cs_main, lock); const CBlockIndex *block = LookupBlockIndex(block_hash); const CBlockIndex *ancestor = LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) { ancestor = nullptr; } return FillBlock(ancestor, ancestor_out, lock); } bool findCommonAncestor(const BlockHash &block_hash1, const BlockHash &block_hash2, const FoundBlock &ancestor_out, const FoundBlock &block1_out, const FoundBlock &block2_out) override { WAIT_LOCK(cs_main, lock); const CBlockIndex *block1 = LookupBlockIndex(block_hash1); const CBlockIndex *block2 = LookupBlockIndex(block_hash2); const CBlockIndex *ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving // output uninitialized. return FillBlock(ancestor, ancestor_out, lock) & FillBlock(block1, block1_out, lock) & FillBlock(block2, block2_out, lock); } void findCoins(std::map &coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const BlockHash &block_hash) override { LOCK(cs_main); return GuessVerificationProgress(Params().TxData(), LookupBlockIndex(block_hash)); } bool hasBlocks(const BlockHash &block_hash, int min_height, std::optional max_height) override { // hasBlocks returns true if all ancestors of block_hash in // specified range have block data (are not pruned), false if any // ancestors in specified range are missing data. // // For simplicity and robustness, min_height and max_height are only // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); if (CBlockIndex *block = LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) { block = block->GetAncestor(*max_height); } for (; block->nStatus.hasData(); block = block->pprev) { // Check pprev to not segfault if min_height is too low if (block->nHeight <= min_height || !block->pprev) { return true; } } } return false; } bool hasDescendantsInMempool(const TxId &txid) override { if (!m_node.mempool) { return false; } LOCK(m_node.mempool->cs); auto it = m_node.mempool->GetIter(txid); return it && (*it)->GetCountWithDescendants() > 1; } bool broadcastTransaction(const Config &config, const CTransactionRef &tx, const Amount &max_tx_fee, bool relay, std::string &err_string) override { const TransactionError err = BroadcastTransaction( m_node, config, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); // Chain clients only care about failures to accept the tx to the // mempool. Disregard non-mempool related failures. Note: this will // need to be updated if BroadcastTransactions() is updated to // return other non-mempool failures that Chain clients do not need // to know about. return err == TransactionError::OK; } void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) override { ancestors = descendants = 0; if (!m_node.mempool) { return; } m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants); } void getPackageLimits(size_t &limit_ancestor_count, size_t &limit_descendant_count) override { limit_ancestor_count = size_t( std::max(1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT))); limit_descendant_count = size_t( std::max(1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT))); } bool checkChainLimits(const CTransactionRef &tx) override { if (!m_node.mempool) { return true; } LockPoints lp; CTxMemPoolEntry entry(tx, Amount(), 0, 0, false, 0, lp); CTxMemPool::setEntries ancestors; auto limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); auto limit_ancestor_size = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; auto limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); auto limit_descendant_size = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; std::string unused_error_string; LOCK(m_node.mempool->cs); return m_node.mempool->CalculateMemPoolAncestors( entry, ancestors, limit_ancestor_count, limit_ancestor_size, limit_descendant_count, limit_descendant_size, unused_error_string); } CFeeRate estimateFee() const override { if (!m_node.mempool) { return {}; } return m_node.mempool->estimateFee(); } CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; } bool havePruned() override { LOCK(cs_main); return ::fHavePruned; } bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return ::ChainstateActive().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string &message) override { ::uiInterface.InitMessage(message); } void initWarning(const bilingual_str &message) override { InitWarning(message); } void initError(const bilingual_str &message) override { InitError(message); } void showProgress(const std::string &title, int progress, bool resume_possible) override { ::uiInterface.ShowProgress(title, progress, resume_possible); } std::unique_ptr handleNotifications( std::shared_ptr notifications) override { return std::make_unique( std::move(notifications)); } void waitForNotificationsIfTipChanged(const BlockHash &old_tip) override { if (!old_tip.IsNull()) { LOCK(::cs_main); if (old_tip == ::ChainActive().Tip()->GetBlockHash()) { return; } } SyncWithValidationInterfaceQueue(); } std::unique_ptr handleRpc(const CRPCCommand &command) override { return std::make_unique(command); } bool rpcEnableDeprecated(const std::string &method) override { return IsDeprecatedRPCEnabled(gArgs, method); } void rpcRunLater(const std::string &name, std::function fn, int64_t seconds) override { RPCRunLater(name, std::move(fn), seconds); } int rpcSerializationFlags() override { return RPCSerializationFlags(); } + util::SettingsValue getRwSetting(const std::string &name) override { + util::SettingsValue result; + gArgs.LockSettings([&](const util::Settings &settings) { + if (const util::SettingsValue *value = + util::FindKey(settings.rw_settings, name)) { + result = *value; + } + }); + return result; + } + bool updateRwSetting(const std::string &name, + const util::SettingsValue &value) override { + gArgs.LockSettings([&](util::Settings &settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + return gArgs.WriteSettingsFile(); + } void requestMempoolTransactions(Notifications ¬ifications) override { if (!m_node.mempool) { return; } LOCK2(::cs_main, m_node.mempool->cs); for (const CTxMemPoolEntry &entry : m_node.mempool->mapTx) { notifications.transactionAddedToMempool(entry.GetSharedTx()); } } const CChainParams ¶ms() const override { return m_params; } NodeContext &m_node; const CChainParams &m_params; }; } // namespace std::unique_ptr MakeChain(NodeContext &node, const CChainParams ¶ms) { return std::make_unique(node, params); } } // namespace interfaces diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 899cc4c8f..92aa4720b 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -1,355 +1,363 @@ // Copyright (c) 2018-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INTERFACES_CHAIN_H #define BITCOIN_INTERFACES_CHAIN_H #include #include +#include // For util::SettingsValue #include #include #include #include #include #include #include class ArgsManager; class CBlock; class CChainParams; class Coin; class Config; class CRPCCommand; class CScheduler; class TxValidationState; enum class MemPoolRemovalReason; struct BlockHash; struct bilingual_str; struct CBlockLocator; struct NodeContext; namespace Consensus { struct Params; } namespace interfaces { class Handler; class Wallet; //! Helper for findBlock to selectively return pieces of block data. class FoundBlock { public: FoundBlock &hash(BlockHash &hash) { m_hash = &hash; return *this; } FoundBlock &height(int &height) { m_height = &height; return *this; } FoundBlock &time(int64_t &time) { m_time = &time; return *this; } FoundBlock &maxTime(int64_t &max_time) { m_max_time = &max_time; return *this; } FoundBlock &mtpTime(int64_t &mtp_time) { m_mtp_time = &mtp_time; return *this; } //! Read block data from disk. If the block exists but doesn't have data //! (for example due to pruning), the CBlock variable will be set to null. FoundBlock &data(CBlock &data) { m_data = &data; return *this; } BlockHash *m_hash = nullptr; int *m_height = nullptr; int64_t *m_time = nullptr; int64_t *m_max_time = nullptr; int64_t *m_mtp_time = nullptr; CBlock *m_data = nullptr; }; //! Interface giving clients (wallet processes, maybe other analysis tools in //! the future) ability to access to the chain state, receive notifications, //! estimate fees, and submit transactions. //! //! TODO: Current chain methods are too low level, exposing too much of the //! internal workings of the bitcoin node, and not being very convenient to use. //! Chain methods should be cleaned up and simplified over time. Examples: //! //! * The initMessages() and showProgress() methods which the wallet uses to //! send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node //! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096). //! //! * The handleRpc, registerRpcs, rpcEnableDeprecated methods and other RPC //! methods can go away if wallets listen for HTTP requests on their own //! ports instead of registering to handle requests on the node HTTP port. //! //! * Move fee estimation queries to an asynchronous interface and let the //! wallet cache it, fee estimation being driven by node mempool, wallet //! should be the consumer. //! //! * The `guessVerificationProgress`, `getBlockHeight`, `getBlockHash`, etc //! methods can go away if rescan logic is moved on the node side, and wallet //! only register rescan request. class Chain { public: virtual ~Chain() {} //! Get current chain height, not including genesis block (returns 0 if //! chain only contains genesis block, std::nullopt if chain does not //! contain any blocks) virtual std::optional getHeight() = 0; //! Get block height above genesis block. Returns 0 for genesis block, //! 1 for following block, and so on. Returns std::nullopt for a block not //! included in the current chain. virtual std::optional getBlockHeight(const BlockHash &hash) = 0; //! Get block hash. Height must be valid or this function will abort. virtual BlockHash getBlockHash(int height) = 0; //! Check that the block is available on disk (i.e. has not been //! pruned), and contains transactions. virtual bool haveBlockOnDisk(int height) = 0; //! Return height of the first block in the chain with timestamp equal //! or greater than the given time and height equal or greater than the //! given height, or std::nullopt if there is no block with a high enough //! timestamp and height. Also return the block hash as an optional output //! parameter (to avoid the cost of a second lookup in case this information //! is needed.) virtual std::optional findFirstBlockWithTimeAndHeight(int64_t time, int height, BlockHash *hash) = 0; //! Get locator for the current chain tip. virtual CBlockLocator getTipLocator() = 0; //! Return height of the highest block on chain in common with the locator, //! which will either be the original block used to create the locator, //! or one of its ancestors. virtual std::optional findLocatorFork(const CBlockLocator &locator) = 0; //! Check if transaction will be final given chain height current time. virtual bool contextualCheckTransactionForCurrentBlock(const CTransaction &tx, TxValidationState &state) = 0; //! Return whether node has the block and optionally return block metadata //! or contents. virtual bool findBlock(const BlockHash &hash, const FoundBlock &block = {}) = 0; //! Find first block in the chain with timestamp >= the given time //! and height >= than the given height, return false if there is no block //! with a high enough timestamp and height. Optionally return block //! information. virtual bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock &block = {}) = 0; //! Find next block if block is part of current chain. Also flag if //! there was a reorg and the specified block hash is no longer in the //! current chain, and optionally return block information. virtual bool findNextBlock(const BlockHash &block_hash, int block_height, const FoundBlock &next = {}, bool *reorg = nullptr) = 0; //! Find ancestor of block at specified height and optionally return //! ancestor information. virtual bool findAncestorByHeight(const BlockHash &block_hash, int ancestor_height, const FoundBlock &ancestor_out = {}) = 0; //! Return whether block descends from a specified ancestor, and //! optionally return ancestor information. virtual bool findAncestorByHash(const BlockHash &block_hash, const BlockHash &ancestor_hash, const FoundBlock &ancestor_out = {}) = 0; //! Find most recent common ancestor between two blocks and optionally //! return block information. virtual bool findCommonAncestor(const BlockHash &block_hash1, const BlockHash &block_hash2, const FoundBlock &ancestor_out = {}, const FoundBlock &block1_out = {}, const FoundBlock &block2_out = {}) = 0; //! Look up unspent output information. Returns coins in the mempool and in //! the current chain UTXO set. Iterates through all the keys in the map and //! populates the values. virtual void findCoins(std::map &coins) = 0; //! Estimate fraction of total transactions verified if blocks up to //! the specified block hash are verified. virtual double guessVerificationProgress(const BlockHash &block_hash) = 0; //! Return true if data is available for all blocks in the specified range //! of blocks. This checks all blocks that are ancestors of block_hash in //! the height range from min_height to max_height, inclusive. virtual bool hasBlocks(const BlockHash &block_hash, int min_height = 0, std::optional max_height = {}) = 0; //! Check if transaction has descendants in mempool. virtual bool hasDescendantsInMempool(const TxId &txid) = 0; //! Transaction is added to memory pool, if the transaction fee is below the //! amount specified by max_tx_fee, and broadcast to all peers if relay is //! set to true. Return false if the transaction could not be added due to //! the fee or for another reason. virtual bool broadcastTransaction(const Config &config, const CTransactionRef &tx, const Amount &max_tx_fee, bool relay, std::string &err_string) = 0; //! Calculate mempool ancestor and descendant counts for the given //! transaction. virtual void getTransactionAncestry(const TxId &txid, size_t &ancestors, size_t &descendants) = 0; //! Get the node's package limits. //! Currently only returns the ancestor and descendant count limits, but //! could be enhanced to return more policy settings. virtual void getPackageLimits(size_t &limit_ancestor_count, size_t &limit_descendant_count) = 0; //! Check if transaction will pass the mempool's chain limits. virtual bool checkChainLimits(const CTransactionRef &tx) = 0; //! Estimate fee virtual CFeeRate estimateFee() const = 0; //! Relay current minimum fee (from -minrelaytxfee settings). virtual CFeeRate relayMinFee() = 0; //! Relay dust fee setting (-dustrelayfee), reflecting lowest rate it's //! economical to spend. virtual CFeeRate relayDustFee() = 0; //! Check if any block has been pruned. virtual bool havePruned() = 0; //! Check if the node is ready to broadcast transactions. virtual bool isReadyToBroadcast() = 0; //! Check if in IBD. virtual bool isInitialBlockDownload() = 0; //! Check if shutdown requested. virtual bool shutdownRequested() = 0; //! Get adjusted time. virtual int64_t getAdjustedTime() = 0; //! Send init message. virtual void initMessage(const std::string &message) = 0; //! Send init warning. virtual void initWarning(const bilingual_str &message) = 0; //! Send init error. virtual void initError(const bilingual_str &message) = 0; //! Send progress indicator. virtual void showProgress(const std::string &title, int progress, bool resume_possible) = 0; //! Chain notifications. class Notifications { public: virtual ~Notifications() {} virtual void transactionAddedToMempool(const CTransactionRef &tx) {} virtual void transactionRemovedFromMempool(const CTransactionRef &ptx, MemPoolRemovalReason reason) {} virtual void blockConnected(const CBlock &block, int height) {} virtual void blockDisconnected(const CBlock &block, int height) {} virtual void updatedBlockTip() {} virtual void chainStateFlushed(const CBlockLocator &locator) {} }; //! Register handler for notifications. virtual std::unique_ptr handleNotifications(std::shared_ptr notifications) = 0; //! Wait for pending notifications to be processed unless block hash points //! to the current chain tip. virtual void waitForNotificationsIfTipChanged(const BlockHash &old_tip) = 0; //! Register handler for RPC. Command is not copied, so reference //! needs to remain valid until Handler is disconnected. virtual std::unique_ptr handleRpc(const CRPCCommand &command) = 0; //! Check if deprecated RPC is enabled. virtual bool rpcEnableDeprecated(const std::string &method) = 0; //! Run function after given number of seconds. Cancel any previous calls //! with same name. virtual void rpcRunLater(const std::string &name, std::function fn, int64_t seconds) = 0; //! Current RPC serialization flags. virtual int rpcSerializationFlags() = 0; + //! Return /settings.json setting value. + virtual util::SettingsValue getRwSetting(const std::string &name) = 0; + + //! Write a setting to /settings.json. + virtual bool updateRwSetting(const std::string &name, + const util::SettingsValue &value) = 0; + //! Synchronously send transactionAddedToMempool notifications about all //! current mempool transactions to the specified handler and return after //! the last one is sent. These notifications aren't coordinated with async //! notifications sent by handleNotifications, so out of date async //! notifications from handleNotifications can arrive during and after //! synchronous notifications from requestMempoolTransactions. Clients need //! to be prepared to handle this by ignoring notifications about unknown //! removed transactions and already added new transactions. virtual void requestMempoolTransactions(Notifications ¬ifications) = 0; //! This Chain's parameters virtual const CChainParams ¶ms() const = 0; }; //! Interface to let node manage chain clients (wallets, or maybe tools for //! monitoring and analysis in the future). class ChainClient { public: virtual ~ChainClient() {} //! Register rpcs. virtual void registerRpcs() = 0; //! Check for errors before loading. virtual bool verify() = 0; //! Load saved state. virtual bool load() = 0; //! Start client execution and provide a scheduler. virtual void start(CScheduler &scheduler) = 0; //! Save state to disk. virtual void flush() = 0; //! Shut down client. virtual void stop() = 0; //! Set mock time. virtual void setMockTime(int64_t time) = 0; }; //! Return implementation of Chain interface. std::unique_ptr MakeChain(NodeContext &node, const CChainParams ¶ms); } // namespace interfaces #endif // BITCOIN_INTERFACES_CHAIN_H diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 407d0501f..4a8f00d26 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -1,256 +1,259 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 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 #include #include #include class CRPCConvertParam { public: std::string methodName; //!< method whose params want conversion int paramIdx; //!< 0-based idx of param to convert std::string paramName; //!< parameter name }; /** * Specify a (method, idx, name) here if the argument is a non-string RPC * argument and needs to be converted from JSON. * * @note Parameter indexes start from 0. */ static const CRPCConvertParam vRPCConvertParams[] = { {"setmocktime", 0, "timestamp"}, {"mockscheduler", 0, "delta_time"}, {"utxoupdatepsbt", 1, "descriptors"}, {"generatetoaddress", 0, "nblocks"}, {"generatetoaddress", 2, "maxtries"}, {"generatetodescriptor", 0, "num_blocks"}, {"generatetodescriptor", 2, "maxtries"}, {"generateblock", 1, "transactions"}, {"getnetworkhashps", 0, "nblocks"}, {"getnetworkhashps", 1, "height"}, {"sendtoaddress", 1, "amount"}, {"sendtoaddress", 4, "subtractfeefromamount"}, {"sendtoaddress", 5, "avoid_reuse"}, {"settxfee", 0, "amount"}, {"sethdseed", 0, "newkeypool"}, {"getreceivedbyaddress", 1, "minconf"}, {"getreceivedbylabel", 1, "minconf"}, {"listreceivedbyaddress", 0, "minconf"}, {"listreceivedbyaddress", 1, "include_empty"}, {"listreceivedbyaddress", 2, "include_watchonly"}, {"listreceivedbylabel", 0, "minconf"}, {"listreceivedbylabel", 1, "include_empty"}, {"listreceivedbylabel", 2, "include_watchonly"}, {"getbalance", 1, "minconf"}, {"getbalance", 2, "include_watchonly"}, {"getbalance", 3, "avoid_reuse"}, {"getblockhash", 0, "height"}, {"waitforblockheight", 0, "height"}, {"waitforblockheight", 1, "timeout"}, {"waitforblock", 1, "timeout"}, {"waitfornewblock", 0, "timeout"}, {"listtransactions", 1, "count"}, {"listtransactions", 2, "skip"}, {"listtransactions", 3, "include_watchonly"}, {"walletpassphrase", 1, "timeout"}, {"getblocktemplate", 0, "template_request"}, {"listsinceblock", 1, "target_confirmations"}, {"listsinceblock", 2, "include_watchonly"}, {"listsinceblock", 3, "include_removed"}, {"sendmany", 1, "amounts"}, {"sendmany", 2, "minconf"}, {"sendmany", 4, "subtractfeefrom"}, {"deriveaddresses", 1, "range"}, {"scantxoutset", 1, "scanobjects"}, {"addmultisigaddress", 0, "nrequired"}, {"addmultisigaddress", 1, "keys"}, {"createmultisig", 0, "nrequired"}, {"createmultisig", 1, "keys"}, {"listunspent", 0, "minconf"}, {"listunspent", 1, "maxconf"}, {"listunspent", 2, "addresses"}, {"listunspent", 3, "include_unsafe"}, {"listunspent", 4, "query_options"}, {"getblock", 1, "verbosity"}, {"getblock", 1, "verbose"}, {"getblockheader", 1, "verbose"}, {"getchaintxstats", 0, "nblocks"}, {"gettransaction", 1, "include_watchonly"}, {"gettransaction", 2, "verbose"}, {"getrawtransaction", 1, "verbose"}, {"createrawtransaction", 0, "inputs"}, {"createrawtransaction", 1, "outputs"}, {"createrawtransaction", 2, "locktime"}, {"signrawtransactionwithkey", 1, "privkeys"}, {"signrawtransactionwithkey", 2, "prevtxs"}, {"signrawtransactionwithwallet", 1, "prevtxs"}, {"sendrawtransaction", 1, "maxfeerate"}, {"testmempoolaccept", 0, "rawtxs"}, {"testmempoolaccept", 1, "maxfeerate"}, {"combinerawtransaction", 0, "txs"}, {"fundrawtransaction", 1, "options"}, {"walletcreatefundedpsbt", 0, "inputs"}, {"walletcreatefundedpsbt", 1, "outputs"}, {"walletcreatefundedpsbt", 2, "locktime"}, {"walletcreatefundedpsbt", 3, "options"}, {"walletcreatefundedpsbt", 4, "bip32derivs"}, {"walletprocesspsbt", 1, "sign"}, {"walletprocesspsbt", 3, "bip32derivs"}, {"createpsbt", 0, "inputs"}, {"createpsbt", 1, "outputs"}, {"createpsbt", 2, "locktime"}, {"combinepsbt", 0, "txs"}, {"joinpsbts", 0, "txs"}, {"finalizepsbt", 1, "extract"}, {"converttopsbt", 1, "permitsigdata"}, {"gettxout", 1, "n"}, {"gettxout", 2, "include_mempool"}, {"gettxoutproof", 0, "txids"}, {"lockunspent", 0, "unlock"}, {"lockunspent", 1, "transactions"}, {"importprivkey", 2, "rescan"}, {"importaddress", 2, "rescan"}, {"importaddress", 3, "p2sh"}, {"importpubkey", 2, "rescan"}, {"importmulti", 0, "requests"}, {"importmulti", 1, "options"}, {"importdescriptors", 0, "requests"}, {"verifychain", 0, "checklevel"}, {"verifychain", 1, "nblocks"}, {"getblockstats", 0, "hash_or_height"}, {"getblockstats", 1, "stats"}, {"pruneblockchain", 0, "height"}, {"keypoolrefill", 0, "newsize"}, {"getrawmempool", 0, "verbose"}, {"prioritisetransaction", 1, "dummy"}, {"prioritisetransaction", 2, "fee_delta"}, {"setban", 2, "bantime"}, {"setban", 3, "absolute"}, {"setnetworkactive", 0, "state"}, {"setwalletflag", 1, "value"}, {"getmempoolancestors", 1, "verbose"}, {"getmempooldescendants", 1, "verbose"}, {"disconnectnode", 1, "nodeid"}, {"logging", 0, "include"}, {"logging", 1, "exclude"}, {"upgradewallet", 0, "version"}, // Echo with conversion (For testing only) {"echojson", 0, "arg0"}, {"echojson", 1, "arg1"}, {"echojson", 2, "arg2"}, {"echojson", 3, "arg3"}, {"echojson", 4, "arg4"}, {"echojson", 5, "arg5"}, {"echojson", 6, "arg6"}, {"echojson", 7, "arg7"}, {"echojson", 8, "arg8"}, {"echojson", 9, "arg9"}, {"rescanblockchain", 0, "start_height"}, {"rescanblockchain", 1, "stop_height"}, {"createwallet", 1, "disable_private_keys"}, {"createwallet", 2, "blank"}, {"createwallet", 4, "avoid_reuse"}, {"createwallet", 5, "descriptors"}, + {"createwallet", 6, "load_on_startup"}, + {"loadwallet", 1, "load_on_startup"}, + {"unloadwallet", 1, "load_on_startup"}, {"getnodeaddresses", 0, "count"}, {"addpeeraddress", 1, "port"}, {"stop", 0, "wait"}, // Avalanche {"addavalanchenode", 0, "nodeid"}, {"buildavalancheproof", 0, "sequence"}, {"buildavalancheproof", 1, "expiration"}, {"buildavalancheproof", 3, "stakes"}, }; class CRPCConvertTable { private: std::set> members; std::set> membersByName; public: CRPCConvertTable(); bool convert(const std::string &method, int idx) { return (members.count(std::make_pair(method, idx)) > 0); } bool convert(const std::string &method, const std::string &name) { return (membersByName.count(std::make_pair(method, name)) > 0); } }; CRPCConvertTable::CRPCConvertTable() { const unsigned int n_elem = (sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0])); for (unsigned int i = 0; i < n_elem; i++) { members.insert(std::make_pair(vRPCConvertParams[i].methodName, vRPCConvertParams[i].paramIdx)); membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName, vRPCConvertParams[i].paramName)); } } static CRPCConvertTable rpcCvtTable; /** * Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, * false, null) as well as objects and arrays. */ UniValue ParseNonRFCJSONValue(const std::string &strVal) { UniValue jVal; if (!jVal.read(std::string("[") + strVal + std::string("]")) || !jVal.isArray() || jVal.size() != 1) { throw std::runtime_error(std::string("Error parsing JSON: ") + strVal); } return jVal[0]; } UniValue RPCConvertValues(const std::string &strMethod, const std::vector &strParams) { UniValue params(UniValue::VARR); for (unsigned int idx = 0; idx < strParams.size(); idx++) { const std::string &strVal = strParams[idx]; if (!rpcCvtTable.convert(strMethod, idx)) { // insert string value directly params.push_back(strVal); } else { // parse string as JSON, insert bool/number/object/etc. value params.push_back(ParseNonRFCJSONValue(strVal)); } } return params; } UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector &strParams) { UniValue params(UniValue::VOBJ); for (const std::string &s : strParams) { size_t pos = s.find('='); if (pos == std::string::npos) { throw(std::runtime_error("No '=' in named argument '" + s + "', this needs to be present for every " "argument (even if it is empty)")); } std::string name = s.substr(0, pos); std::string value = s.substr(pos + 1); if (!rpcCvtTable.convert(strMethod, name)) { // insert string value directly params.pushKV(name, value); } else { // parse string as JSON, insert bool/number/object/etc. value params.pushKV(name, ParseNonRFCJSONValue(value)); } } return params; } diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 283f50bbe..9c45c8944 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -1,212 +1,221 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2017 The Bitcoin Core developers // Copyright (c) 2018-2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include class WalletInit : public WalletInitInterface { public: //! Was the wallet component compiled in. bool HasWalletSupport() const override { return true; } //! Return the wallets help message. void AddWalletOptions(ArgsManager &argsman) const override; //! Wallets parameter interaction bool ParameterInteraction() const override; //! Add wallets that should be opened to list of chain clients. void Construct(NodeContext &node) const override; }; const WalletInitInterface &g_wallet_init_interface = WalletInit(); void WalletInit::AddWalletOptions(ArgsManager &argsman) const { argsman.AddArg( "-avoidpartialspends", strprintf("Group outputs by address, selecting all or none, instead of " "selecting on a per-output basis. Privacy is improved as an " "address is only used once (unless someone sends to it after " "spending from it), but may result in slightly higher fees " "as suboptimal coin selection may result due to the added " "limitation (default: %u (always enabled for wallets with " "\"avoid_reuse\" enabled))", DEFAULT_AVOIDPARTIALSPENDS), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-disablewallet", "Do not load the wallet and disable wallet RPC calls", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); const auto &ticker = Currency::get().ticker; argsman.AddArg("-fallbackfee=", strprintf("A fee rate (in %s/kB) that will be used when fee " "estimation has insufficient data. 0 to entirely " "disable the fallbackfee feature. (default: %s)", ticker, FormatMoney(DEFAULT_FALLBACK_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-keypool=", strprintf("Set key pool size to (default: %u). Warning: Smaller " "sizes may increase the risk of losing funds when restoring " "from an old backup, if none of the addresses in the " "original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-maxapsfee=", strprintf( "Spend up to this amount in additional (absolute) fees (in %s) if " "it allows the use of partial spend avoidance (default: %s)", ticker, FormatMoney(DEFAULT_MAX_AVOIDPARTIALSPEND_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-maxtxfee=", strprintf("Maximum total fees (in %s) to use in a single wallet " "transaction or raw transaction; setting this too low may " "abort large transactions (default: %s)", ticker, FormatMoney(DEFAULT_TRANSACTION_MAXFEE)), ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-mintxfee=", strprintf("Fees (in %s/kB) smaller than this are considered " "zero fee for transaction creation (default: %s)", ticker, FormatMoney(DEFAULT_TRANSACTION_MINFEE_PER_KB)), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-paytxfee=", strprintf( "Fee (in %s/kB) to add to transactions you send (default: %s)", ticker, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-spendzeroconfchange", strprintf( "Spend unconfirmed change when sending transactions (default: %d)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg( "-wallet=", "Specify wallet database path. Can be specified multiple " "times to load multiple wallets. Path is interpreted relative " "to if it is not absolute, and will be created if " "it does not exist (as a directory containing a wallet.dat " "file and log files). For backwards compatibility this will " "also accept names of existing data files in .)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); argsman.AddArg( "-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %d)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-walletdir=", "Specify directory to hold wallets (default: " "/wallets if it exists, otherwise )", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); #if defined(HAVE_SYSTEM) argsman.AddArg( "-walletnotify=", "Execute command when a wallet transaction changes. %s in cmd " "is replaced by TxID and %w is replaced by wallet name. %w is " "not currently implemented on windows. On systems where %w is " "supported, it should NOT be quoted because this would break " "shell escaping used to invoke the command.", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif argsman.AddArg( "-dblogsize=", strprintf("Flush wallet database activity from memory to disk " "log every megabytes (default: %u)", DEFAULT_WALLET_DBLOGSIZE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg( "-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %d)", DEFAULT_FLUSHWALLET), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-privdb", strprintf("Sets the DB_PRIVATE flag in the wallet db " "environment (default: %d)", DEFAULT_WALLET_PRIVDB), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddArg("-walletrejectlongchains", strprintf("Wallet will not create transactions that violate " "mempool chain limits (default: %d)", DEFAULT_WALLET_REJECT_LONG_CHAINS), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::WALLET_DEBUG_TEST); argsman.AddHiddenArgs({"-zapwallettxes"}); } bool WalletInit::ParameterInteraction() const { if (gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { for (const std::string &wallet : gArgs.GetArgs("-wallet")) { LogPrintf("%s: parameter interaction: -disablewallet -> ignoring " "-wallet=%s\n", __func__, wallet); } return true; } if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) { LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting " "-walletbroadcast=0\n", __func__); } if (gArgs.IsArgSet("-zapwallettxes")) { return InitError( Untranslated("-zapwallettxes has been removed. If you are " "attempting to remove a stuck transaction from your " "wallet, please use abandontransaction instead.")); } if (gArgs.GetBoolArg("-sysperms", false)) { return InitError( Untranslated("-sysperms is not allowed in combination with enabled " "wallet functionality")); } return true; } void WalletInit::Construct(NodeContext &node) const { ArgsManager &args = *Assert(node.args); if (args.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { LogPrintf("Wallet disabled!\n"); return; } - args.SoftSetArg("-wallet", ""); + // If there's no -wallet setting with a list of wallets to load, set it to + // load the default "" wallet. + if (!args.IsArgSet("wallet")) { + args.LockSettings([&](util::Settings &settings) { + util::SettingsValue wallets(util::SettingsValue::VARR); + wallets.push_back(""); // Default wallet name is "" + settings.rw_settings["wallet"] = wallets; + }); + } auto wallet_client = interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet")); node.wallet_client = wallet_client.get(); node.chain_clients.emplace_back(std::move(wallet_client)); } diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 04bac7d35..8001d50da 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -1,144 +1,179 @@ // 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 #include #include #include #include #include #include #include +#include + bool VerifyWallets(interfaces::Chain &chain, const std::vector &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)...").translated); // Keep track of each wallet absolute path to detect duplicates. std::set 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; } bilingual_str error_string; std::vector warnings; bool verify_success = CWallet::Verify(chain, location, error_string, warnings); if (!warnings.empty()) { chain.initWarning(Join(warnings, Untranslated("\n"))); } if (!verify_success) { chain.initError(error_string); return false; } } return true; } bool LoadWallets(interfaces::Chain &chain, const std::vector &wallet_files) { try { for (const std::string &walletFile : wallet_files) { bilingual_str error; std::vector warnings; std::shared_ptr pwallet = CWallet::CreateWalletFromFile( chain, WalletLocation(walletFile), error, warnings); if (!warnings.empty()) { chain.initWarning(Join(warnings, Untranslated("\n"))); } if (!pwallet) { chain.initError(error); return false; } AddWallet(pwallet); } return true; } catch (const std::runtime_error &e) { chain.initError(Untranslated(e.what())); return false; } } void StartWallets(CScheduler &scheduler, const ArgsManager &args) { for (const std::shared_ptr &pwallet : GetWallets()) { pwallet->postInitProcess(); } // Schedule periodic wallet flushes and tx rebroadcasts if (args.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) { scheduler.scheduleEvery( [] { MaybeCompactWalletDB(); return true; }, std::chrono::milliseconds{500}); } scheduler.scheduleEvery( [] { MaybeResendWalletTxs(); return true; }, std::chrono::milliseconds{1000}); } void FlushWallets() { for (const std::shared_ptr &pwallet : GetWallets()) { pwallet->Flush(); } } void StopWallets() { for (const std::shared_ptr &pwallet : GetWallets()) { pwallet->Close(); } } void UnloadWallets() { auto wallets = GetWallets(); while (!wallets.empty()) { auto wallet = wallets.back(); wallets.pop_back(); RemoveWallet(wallet); UnloadWallet(std::move(wallet)); } } + +bool AddWalletSetting(interfaces::Chain &chain, + const std::string &wallet_name) { + util::SettingsValue setting_value = chain.getRwSetting("wallet"); + if (!setting_value.isArray()) { + setting_value.setArray(); + } + for (const util::SettingsValue &value : setting_value.getValues()) { + if (value.isStr() && value.get_str() == wallet_name) { + return true; + } + } + setting_value.push_back(wallet_name); + return chain.updateRwSetting("wallet", setting_value); +} + +bool RemoveWalletSetting(interfaces::Chain &chain, + const std::string &wallet_name) { + util::SettingsValue setting_value = chain.getRwSetting("wallet"); + if (!setting_value.isArray()) { + return true; + } + util::SettingsValue new_value(util::SettingsValue::VARR); + for (const util::SettingsValue &value : setting_value.getValues()) { + if (!value.isStr() || value.get_str() != wallet_name) { + new_value.push_back(value); + } + } + if (new_value.size() == setting_value.size()) { + return true; + } + return chain.updateRwSetting("wallet", new_value); +} diff --git a/src/wallet/load.h b/src/wallet/load.h index dd6893e16..78dbf28f7 100644 --- a/src/wallet/load.h +++ b/src/wallet/load.h @@ -1,40 +1,48 @@ // 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 #include class ArgsManager; class CScheduler; namespace interfaces { class Chain; } // namespace interfaces //! Responsible for reading and validating the -wallet arguments and verifying //! the wallet database. bool VerifyWallets(interfaces::Chain &chain, const std::vector &wallet_files); //! Load wallet databases. bool LoadWallets(interfaces::Chain &chain, const std::vector &wallet_files); //! Complete startup of wallets. void StartWallets(CScheduler &scheduler, const ArgsManager &args); //! Flush all wallets in preparation for shutdown. void FlushWallets(); //! Stop all wallets. Wallets will be flushed first. void StopWallets(); //! Close all wallets. void UnloadWallets(); +//! Add wallet name to persistent configuration so it will be loaded on startup. +bool AddWalletSetting(interfaces::Chain &chain, const std::string &wallet_name); + +//! Remove wallet name from persistent configuration so it will not be loaded on +//! startup. +bool RemoveWalletSetting(interfaces::Chain &chain, + const std::string &wallet_name); + #endif // BITCOIN_WALLET_LOAD_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 96b56ef24..ae42e78f8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,4983 +1,5030 @@ // Copyright (c) 2010 Satoshi Nakamoto // 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. #include #include // for GetConsensus. #include #include #include #include #include #include #include #include #include #include #include #include #include #include