aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/init.cpp24
-rw-r--r--src/init.h5
-rw-r--r--src/interfaces/chain.cpp21
-rw-r--r--src/interfaces/chain.h7
-rw-r--r--src/interfaces/node.cpp4
-rw-r--r--src/interfaces/node.h12
-rw-r--r--src/net_processing.cpp53
-rw-r--r--src/protocol.cpp1
-rw-r--r--src/protocol.h3
-rw-r--r--src/qt/bitcoin.cpp10
-rw-r--r--src/qt/bitcoin.h10
-rw-r--r--src/qt/bitcoingui.cpp8
-rw-r--r--src/qt/bitcoingui.h3
-rw-r--r--src/qt/rpcconsole.cpp6
-rw-r--r--src/qt/rpcconsole.h2
-rw-r--r--src/qt/test/apptests.cpp1
-rw-r--r--src/rpc/blockchain.cpp6
-rw-r--r--src/rpc/client.cpp3
-rw-r--r--src/rpc/mining.cpp22
-rw-r--r--src/rpc/misc.cpp145
-rw-r--r--src/rpc/net.cpp6
-rw-r--r--src/rpc/rawtransaction.cpp10
-rw-r--r--src/rpc/server.cpp9
-rw-r--r--src/rpc/util.cpp4
-rw-r--r--src/script/sign.cpp2
-rw-r--r--src/test/transaction_tests.cpp2
-rw-r--r--src/test/util_tests.cpp121
-rw-r--r--src/wallet/coinselection.cpp43
-rw-r--r--src/wallet/coinselection.h8
-rw-r--r--src/wallet/init.cpp11
-rw-r--r--src/wallet/load.cpp25
-rw-r--r--src/wallet/load.h6
-rw-r--r--src/wallet/rpcwallet.cpp43
-rw-r--r--src/wallet/salvage.cpp27
-rw-r--r--src/wallet/salvage.h4
-rw-r--r--src/wallet/wallet.cpp45
-rw-r--r--src/wallet/walletdb.h4
-rw-r--r--src/wallet/wallettool.cpp34
38 files changed, 461 insertions, 289 deletions
diff --git a/src/init.cpp b/src/init.cpp
index 08944b79a5..f5aef08211 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -24,6 +24,7 @@
#include <index/blockfilterindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
+#include <interfaces/node.h>
#include <key.h>
#include <miner.h>
#include <net.h>
@@ -999,11 +1000,13 @@ bool AppInitParameterInteraction()
}
}
- // Basic filters are the only supported filters. The basic filters index must be enabled
- // to serve compact filters
- if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS) &&
- g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {
- return InitError(_("Cannot set -peerblockfilters without -blockfilterindex."));
+ // Signal NODE_COMPACT_FILTERS if peerblockfilters and basic filters index are both enabled.
+ if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS)) {
+ if (g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {
+ return InitError(_("Cannot set -peerblockfilters without -blockfilterindex."));
+ }
+
+ nLocalServices = ServiceFlags(nLocalServices | NODE_COMPACT_FILTERS);
}
// if using block pruning, then disallow txindex
@@ -1242,7 +1245,7 @@ bool AppInitLockDataDirectory()
return true;
}
-bool AppInitMain(const util::Ref& context, NodeContext& node)
+bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
{
const CChainParams& chainparams = Params();
// ********************************************************* Step 4a: application initialization
@@ -1877,6 +1880,15 @@ bool AppInitMain(const util::Ref& context, NodeContext& node)
LOCK(cs_main);
LogPrintf("block tree size = %u\n", chainman.BlockIndex().size());
chain_active_height = chainman.ActiveChain().Height();
+ if (tip_info) {
+ tip_info->block_height = chain_active_height;
+ tip_info->block_time = chainman.ActiveChain().Tip() ? chainman.ActiveChain().Tip()->GetBlockTime() : Params().GenesisBlock().GetBlockTime();
+ tip_info->verification_progress = GuessVerificationProgress(Params().TxData(), chainman.ActiveChain().Tip());
+ }
+ if (tip_info && ::pindexBestHeader) {
+ tip_info->header_height = ::pindexBestHeader->nHeight;
+ tip_info->header_time = ::pindexBestHeader->GetBlockTime();
+ }
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
diff --git a/src/init.h b/src/init.h
index 33fe96e8ea..20008ba5be 100644
--- a/src/init.h
+++ b/src/init.h
@@ -11,6 +11,9 @@
#include <util/system.h>
struct NodeContext;
+namespace interfaces {
+struct BlockAndHeaderTipInfo;
+}
namespace boost {
class thread_group;
} // namespace boost
@@ -54,7 +57,7 @@ bool AppInitLockDataDirectory();
* @note This should only be done after daemonization. Call Shutdown() if this function fails.
* @pre Parameters should be parsed and config file should be read, AppInitLockDataDirectory should have been called.
*/
-bool AppInitMain(const util::Ref& context, NodeContext& node);
+bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr);
/**
* Register all arguments with the ArgsManager
diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp
index d49e4454af..313c1265de 100644
--- a/src/interfaces/chain.cpp
+++ b/src/interfaces/chain.cpp
@@ -372,6 +372,27 @@ public:
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& notifications) override
{
LOCK2(::cs_main, ::mempool.cs);
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index bbeb0fa801..053d40335f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -7,6 +7,7 @@
#include <optional.h> // For Optional and nullopt
#include <primitives/transaction.h> // For CTransactionRef
+#include <util/settings.h> // For util::SettingsValue
#include <functional>
#include <memory>
@@ -269,6 +270,12 @@ public:
//! Current RPC serialization flags.
virtual int rpcSerializationFlags() = 0;
+ //! Return <datadir>/settings.json setting value.
+ virtual util::SettingsValue getRwSetting(const std::string& name) = 0;
+
+ //! Write a setting to <datadir>/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
diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp
index 969767b90f..21400d00f8 100644
--- a/src/interfaces/node.cpp
+++ b/src/interfaces/node.cpp
@@ -80,10 +80,10 @@ public:
return AppInitBasicSetup() && AppInitParameterInteraction() && AppInitSanityChecks() &&
AppInitLockDataDirectory();
}
- bool appInitMain() override
+ bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override
{
m_context->chain = MakeChain(*m_context);
- return AppInitMain(m_context_ref, *m_context);
+ return AppInitMain(m_context_ref, *m_context, tip_info);
}
void appShutdown() override
{
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index cd3cfe487d..753f3e6b13 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -39,6 +39,16 @@ class Handler;
class Wallet;
struct BlockTip;
+//! Block and header tip information
+struct BlockAndHeaderTipInfo
+{
+ int block_height;
+ int64_t block_time;
+ int header_height;
+ int64_t header_time;
+ double verification_progress;
+};
+
//! Top-level interface for a bitcoin node (bitcoind process).
class Node
{
@@ -96,7 +106,7 @@ public:
virtual bool baseInitialize() = 0;
//! Start node.
- virtual bool appInitMain() = 0;
+ virtual bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info = nullptr) = 0;
//! Stop node.
virtual void appShutdown() = 0;
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 3e06f1fff0..7b83583e41 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2091,7 +2091,7 @@ void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uin
*
* May disconnect from the peer in the case of a bad request.
*
- * @param[in] pfrom The peer that we received the request from
+ * @param[in] peer The peer that we received the request from
* @param[in] chain_params Chain parameters
* @param[in] filter_type The filter type the request is for. Must be basic filters.
* @param[in] start_height The start height for the request
@@ -2101,7 +2101,7 @@ void static ProcessOrphanTx(CConnman& connman, CTxMemPool& mempool, std::set<uin
* @param[out] filter_index The filter index, if the request can be serviced.
* @return True if the request can be serviced.
*/
-static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_params,
+static bool PrepareBlockFilterRequest(CNode& peer, const CChainParams& chain_params,
BlockFilterType filter_type, uint32_t start_height,
const uint256& stop_hash, uint32_t max_height_diff,
const CBlockIndex*& stop_index,
@@ -2109,11 +2109,11 @@ static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_pa
{
const bool supported_filter_type =
(filter_type == BlockFilterType::BASIC &&
- gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS));
+ (peer.GetLocalServices() & NODE_COMPACT_FILTERS));
if (!supported_filter_type) {
LogPrint(BCLog::NET, "peer %d requested unsupported block filter type: %d\n",
- pfrom.GetId(), static_cast<uint8_t>(filter_type));
- pfrom.fDisconnect = true;
+ peer.GetId(), static_cast<uint8_t>(filter_type));
+ peer.fDisconnect = true;
return false;
}
@@ -2124,8 +2124,8 @@ static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_pa
// Check that the stop block exists and the peer would be allowed to fetch it.
if (!stop_index || !BlockRequestAllowed(stop_index, chain_params.GetConsensus())) {
LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n",
- pfrom.GetId(), stop_hash.ToString());
- pfrom.fDisconnect = true;
+ peer.GetId(), stop_hash.ToString());
+ peer.fDisconnect = true;
return false;
}
}
@@ -2134,14 +2134,14 @@ static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_pa
if (start_height > stop_height) {
LogPrint(BCLog::NET, "peer %d sent invalid getcfilters/getcfheaders with " /* Continued */
"start height %d and stop height %d\n",
- pfrom.GetId(), start_height, stop_height);
- pfrom.fDisconnect = true;
+ peer.GetId(), start_height, stop_height);
+ peer.fDisconnect = true;
return false;
}
if (stop_height - start_height >= max_height_diff) {
LogPrint(BCLog::NET, "peer %d requested too many cfilters/cfheaders: %d / %d\n",
- pfrom.GetId(), stop_height - start_height + 1, max_height_diff);
- pfrom.fDisconnect = true;
+ peer.GetId(), stop_height - start_height + 1, max_height_diff);
+ peer.fDisconnect = true;
return false;
}
@@ -2159,12 +2159,12 @@ static bool PrepareBlockFilterRequest(CNode& pfrom, const CChainParams& chain_pa
*
* May disconnect from the peer in the case of a bad request.
*
- * @param[in] pfrom The peer that we received the request from
+ * @param[in] peer The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
-static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
+static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params,
CConnman& connman)
{
uint8_t filter_type_ser;
@@ -2177,13 +2177,12 @@ static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainPar
const CBlockIndex* stop_index;
BlockFilterIndex* filter_index;
- if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
+ if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash,
MAX_GETCFILTERS_SIZE, stop_index, filter_index)) {
return;
}
std::vector<BlockFilter> filters;
-
if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) {
LogPrint(BCLog::NET, "Failed to find block filter in index: filter_type=%s, start_height=%d, stop_hash=%s\n",
BlockFilterTypeName(filter_type), start_height, stop_hash.ToString());
@@ -2191,9 +2190,9 @@ static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainPar
}
for (const auto& filter : filters) {
- CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
+ CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion())
.Make(NetMsgType::CFILTER, filter);
- connman.PushMessage(&pfrom, std::move(msg));
+ connman.PushMessage(&peer, std::move(msg));
}
}
@@ -2202,12 +2201,12 @@ static void ProcessGetCFilters(CNode& pfrom, CDataStream& vRecv, const CChainPar
*
* May disconnect from the peer in the case of a bad request.
*
- * @param[in] pfrom The peer that we received the request from
+ * @param[in] peer The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
-static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
+static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params,
CConnman& connman)
{
uint8_t filter_type_ser;
@@ -2220,7 +2219,7 @@ static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainPa
const CBlockIndex* stop_index;
BlockFilterIndex* filter_index;
- if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, start_height, stop_hash,
+ if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, start_height, stop_hash,
MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) {
return;
}
@@ -2243,13 +2242,13 @@ static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainPa
return;
}
- CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
+ CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion())
.Make(NetMsgType::CFHEADERS,
filter_type_ser,
stop_index->GetBlockHash(),
prev_header,
filter_hashes);
- connman.PushMessage(&pfrom, std::move(msg));
+ connman.PushMessage(&peer, std::move(msg));
}
/**
@@ -2257,12 +2256,12 @@ static void ProcessGetCFHeaders(CNode& pfrom, CDataStream& vRecv, const CChainPa
*
* May disconnect from the peer in the case of a bad request.
*
- * @param[in] pfrom The peer that we received the request from
+ * @param[in] peer The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
-static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainParams& chain_params,
+static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainParams& chain_params,
CConnman& connman)
{
uint8_t filter_type_ser;
@@ -2274,7 +2273,7 @@ static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainPa
const CBlockIndex* stop_index;
BlockFilterIndex* filter_index;
- if (!PrepareBlockFilterRequest(pfrom, chain_params, filter_type, /*start_height=*/0, stop_hash,
+ if (!PrepareBlockFilterRequest(peer, chain_params, filter_type, /*start_height=*/0, stop_hash,
/*max_height_diff=*/std::numeric_limits<uint32_t>::max(),
stop_index, filter_index)) {
return;
@@ -2295,12 +2294,12 @@ static void ProcessGetCFCheckPt(CNode& pfrom, CDataStream& vRecv, const CChainPa
}
}
- CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
+ CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion())
.Make(NetMsgType::CFCHECKPT,
filter_type_ser,
stop_index->GetBlockHash(),
headers);
- connman.PushMessage(&pfrom, std::move(msg));
+ connman.PushMessage(&peer, std::move(msg));
}
void ProcessMessage(
diff --git a/src/protocol.cpp b/src/protocol.cpp
index 5a91acee0f..c989aa3902 100644
--- a/src/protocol.cpp
+++ b/src/protocol.cpp
@@ -217,6 +217,7 @@ static std::string serviceFlagToStr(size_t bit)
case NODE_GETUTXO: return "GETUTXO";
case NODE_BLOOM: return "BLOOM";
case NODE_WITNESS: return "WITNESS";
+ case NODE_COMPACT_FILTERS: return "COMPACT_FILTERS";
case NODE_NETWORK_LIMITED: return "NETWORK_LIMITED";
// Not using default, so we get warned when a case is missing
}
diff --git a/src/protocol.h b/src/protocol.h
index 1d0adaae6e..0bef12ee62 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -272,6 +272,9 @@ enum ServiceFlags : uint64_t {
// NODE_WITNESS indicates that a node can be asked for blocks and transactions including
// witness data.
NODE_WITNESS = (1 << 3),
+ // NODE_COMPACT_FILTERS means the node will service basic block filter requests.
+ // See BIP157 and BIP158 for details on how this is implemented.
+ NODE_COMPACT_FILTERS = (1 << 6),
// NODE_NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only
// serving the last 288 (2 day) blocks
// See BIP159 for details on how this is implemented.
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 4f1e0056be..f53fcc41f3 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -81,6 +81,7 @@ static void RegisterMetaTypes()
qRegisterMetaType<std::function<void()>>("std::function<void()>");
qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon");
+ qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
}
static QString GetLangTerritory()
@@ -164,8 +165,9 @@ void BitcoinCore::initialize()
{
qDebug() << __func__ << ": Running initialization in thread";
util::ThreadRename("qt-init");
- bool rv = m_node.appInitMain();
- Q_EMIT initializeResult(rv);
+ interfaces::BlockAndHeaderTipInfo tip_info;
+ bool rv = m_node.appInitMain(&tip_info);
+ Q_EMIT initializeResult(rv, tip_info);
} catch (const std::exception& e) {
handleRunawayException(&e);
} catch (...) {
@@ -342,7 +344,7 @@ void BitcoinApplication::requestShutdown()
Q_EMIT requestedShutdown();
}
-void BitcoinApplication::initializeResult(bool success)
+void BitcoinApplication::initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info)
{
qDebug() << __func__ << ": Initialization result: " << success;
// Set exit result.
@@ -352,7 +354,7 @@ void BitcoinApplication::initializeResult(bool success)
// Log this only after AppInitMain finishes, as then logging setup is guaranteed complete
qInfo() << "Platform customization:" << platformStyle->getName();
clientModel = new ClientModel(m_node, optionsModel);
- window->setClientModel(clientModel);
+ window->setClientModel(clientModel, &tip_info);
#ifdef ENABLE_WALLET
if (WalletModel::isWalletEnabled()) {
m_wallet_controller = new WalletController(*clientModel, platformStyle, this);
diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h
index 077a37fde5..20c6dfc047 100644
--- a/src/qt/bitcoin.h
+++ b/src/qt/bitcoin.h
@@ -12,6 +12,8 @@
#include <QApplication>
#include <memory>
+#include <interfaces/node.h>
+
class BitcoinGUI;
class ClientModel;
class NetworkStyle;
@@ -21,10 +23,6 @@ class PlatformStyle;
class WalletController;
class WalletModel;
-namespace interfaces {
-class Handler;
-class Node;
-} // namespace interfaces
/** Class encapsulating Bitcoin Core startup and shutdown.
* Allows running startup and shutdown in a different thread from the UI thread.
@@ -40,7 +38,7 @@ public Q_SLOTS:
void shutdown();
Q_SIGNALS:
- void initializeResult(bool success);
+ void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);
void shutdownResult();
void runawayException(const QString &message);
@@ -91,7 +89,7 @@ public:
void setupPlatformStyle();
public Q_SLOTS:
- void initializeResult(bool success);
+ void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);
void shutdownResult();
/// Handle runaway exceptions. Shows a message box with the problem and quits the program.
void handleRunawayException(const QString &message);
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index ebcc04a5eb..56adbf249a 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -574,7 +574,7 @@ void BitcoinGUI::createToolBars()
}
}
-void BitcoinGUI::setClientModel(ClientModel *_clientModel)
+void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndHeaderTipInfo* tip_info)
{
this->clientModel = _clientModel;
if(_clientModel)
@@ -588,8 +588,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel)
connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections);
connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive);
- modalOverlay->setKnownBestHeight(_clientModel->getHeaderTipHeight(), QDateTime::fromTime_t(_clientModel->getHeaderTipTime()));
- setNumBlocks(m_node.getNumBlocks(), QDateTime::fromTime_t(m_node.getLastBlockTime()), m_node.getVerificationProgress(), false, SynchronizationState::INIT_DOWNLOAD);
+ modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromTime_t(tip_info->header_time));
+ setNumBlocks(tip_info->block_height, QDateTime::fromTime_t(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD);
connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks);
// Receive and report messages from client model
@@ -600,7 +600,7 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel)
// Show progress dialog
connect(_clientModel, &ClientModel::showProgress, this, &BitcoinGUI::showProgress);
- rpcConsole->setClientModel(_clientModel);
+ rpcConsole->setClientModel(_clientModel, tip_info->block_height, tip_info->block_time, tip_info->verification_progress);
updateProxyIcon();
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 697e83e772..4c55f28693 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -43,6 +43,7 @@ enum class SynchronizationState;
namespace interfaces {
class Handler;
class Node;
+struct BlockAndHeaderTipInfo;
}
QT_BEGIN_NAMESPACE
@@ -75,7 +76,7 @@ public:
/** Set the client model.
The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic.
*/
- void setClientModel(ClientModel *clientModel);
+ void setClientModel(ClientModel *clientModel = nullptr, interfaces::BlockAndHeaderTipInfo* tip_info = nullptr);
#ifdef ENABLE_WALLET
void setWalletController(WalletController* wallet_controller);
#endif
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 821a337a62..a14fae6460 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -556,7 +556,7 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
return QWidget::eventFilter(obj, event);
}
-void RPCConsole::setClientModel(ClientModel *model)
+void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_t bestblock_date, double verification_progress)
{
clientModel = model;
@@ -576,13 +576,13 @@ void RPCConsole::setClientModel(ClientModel *model)
setNumConnections(model->getNumConnections());
connect(model, &ClientModel::numConnectionsChanged, this, &RPCConsole::setNumConnections);
- interfaces::Node& node = clientModel->node();
- setNumBlocks(node.getNumBlocks(), QDateTime::fromTime_t(node.getLastBlockTime()), node.getVerificationProgress(), false);
+ setNumBlocks(bestblock_height, QDateTime::fromTime_t(bestblock_date), verification_progress, false);
connect(model, &ClientModel::numBlocksChanged, this, &RPCConsole::setNumBlocks);
updateNetworkState();
connect(model, &ClientModel::networkActiveChanged, this, &RPCConsole::setNetworkActive);
+ interfaces::Node& node = clientModel->node();
updateTrafficStats(node.getTotalBytesRecv(), node.getTotalBytesSent());
connect(model, &ClientModel::bytesChanged, this, &RPCConsole::updateTrafficStats);
diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h
index de8e37cca2..280c5bd71a 100644
--- a/src/qt/rpcconsole.h
+++ b/src/qt/rpcconsole.h
@@ -46,7 +46,7 @@ public:
return RPCParseCommandLine(&node, strResult, strCommand, true, pstrFilteredOut, wallet_model);
}
- void setClientModel(ClientModel *model);
+ void setClientModel(ClientModel *model = nullptr, int bestblock_height = 0, int64_t bestblock_date = 0, double verification_progress = 0.0);
void addWallet(WalletModel * const walletModel);
void removeWallet(WalletModel* const walletModel);
diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp
index b880a99baf..0b5c341548 100644
--- a/src/qt/test/apptests.cpp
+++ b/src/qt/test/apptests.cpp
@@ -67,6 +67,7 @@ void AppTests::appTests()
return GetDataDir() / "blocks";
}());
+ qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
m_app.parameterSetup();
m_app.createOptionsModel(true /* reset settings */);
QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().NetworkIDString()));
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f27373b57c..868ff88d08 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2407,7 +2407,7 @@ static const CRPCCommand commands[] =
{ "hidden", "dumptxoutset", &dumptxoutset, {"path"} },
};
// clang-format on
-
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- t.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
}
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index d61e02aee2..4d08671bd2 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -173,6 +173,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "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" },
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index fee6a893eb..76aa9dbfc1 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -236,6 +236,17 @@ static UniValue generatetodescriptor(const JSONRPCRequest& request)
return generateBlocks(chainman, mempool, coinbase_script, num_blocks, max_tries);
}
+static UniValue generate(const JSONRPCRequest& request)
+{
+ const std::string help_str{"generate ( nblocks maxtries ) has been replaced by the -generate cli option. Refer to -help for more information."};
+
+ if (request.fHelp) {
+ throw std::runtime_error(help_str);
+ } else {
+ throw JSONRPCError(RPC_METHOD_NOT_FOUND, help_str);
+ }
+}
+
static UniValue generatetoaddress(const JSONRPCRequest& request)
{
RPCHelpMan{"generatetoaddress",
@@ -1019,7 +1030,7 @@ static UniValue estimatesmartfee(const JSONRPCRequest& request)
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "feerate", /* optional */ true, "estimate fee rate in " + CURRENCY_UNIT + "/kB (only present if no errors were encountered)"},
- {RPCResult::Type::ARR, "errors", "Errors encountered during processing",
+ {RPCResult::Type::ARR, "errors", /* optional */ true, "Errors encountered during processing (if there are any)",
{
{RPCResult::Type::STR, "", "error"},
}},
@@ -1098,7 +1109,7 @@ static UniValue estimaterawfee(const JSONRPCRequest& request)
{
{RPCResult::Type::ELISION, "", ""},
}},
- {RPCResult::Type::ARR, "errors", /* optional */ true, "Errors encountered during processing",
+ {RPCResult::Type::ARR, "errors", /* optional */ true, "Errors encountered during processing (if there are any)",
{
{RPCResult::Type::STR, "error", ""},
}},
@@ -1198,9 +1209,10 @@ static const CRPCCommand commands[] =
{ "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} },
{ "hidden", "estimaterawfee", &estimaterawfee, {"conf_target", "threshold"} },
+ { "hidden", "generate", &generate, {} },
};
// clang-format on
-
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- t.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
}
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 53d38f4e11..ff31bee1e3 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -27,9 +27,9 @@
#include <univalue.h>
-static UniValue validateaddress(const JSONRPCRequest& request)
+static RPCHelpMan validateaddress()
{
- RPCHelpMan{"validateaddress",
+ return RPCHelpMan{"validateaddress",
"\nReturn information about the given bitcoin address.\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to validate"},
@@ -50,8 +50,8 @@ static UniValue validateaddress(const JSONRPCRequest& request)
HelpExampleCli("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
HelpExampleRpc("validateaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
CTxDestination dest = DecodeDestination(request.params[0].get_str());
bool isValid = IsValidDestination(dest);
@@ -69,11 +69,13 @@ static UniValue validateaddress(const JSONRPCRequest& request)
ret.pushKVs(detail);
}
return ret;
+},
+ };
}
-static UniValue createmultisig(const JSONRPCRequest& request)
+static RPCHelpMan createmultisig()
{
- RPCHelpMan{"createmultisig",
+ return RPCHelpMan{"createmultisig",
"\nCreates a multi-signature address with n signature of m keys required.\n"
"It returns a json object with the address and redeemScript.\n",
{
@@ -98,8 +100,8 @@ static UniValue createmultisig(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
int required = request.params[0].get_int();
// Get the public keys
@@ -135,11 +137,13 @@ static UniValue createmultisig(const JSONRPCRequest& request)
result.pushKV("descriptor", descriptor->ToString());
return result;
+},
+ };
}
-UniValue getdescriptorinfo(const JSONRPCRequest& request)
+static RPCHelpMan getdescriptorinfo()
{
- RPCHelpMan{"getdescriptorinfo",
+ return RPCHelpMan{"getdescriptorinfo",
{"\nAnalyses a descriptor.\n"},
{
{"descriptor", RPCArg::Type::STR, RPCArg::Optional::NO, "The descriptor."},
@@ -157,8 +161,9 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request)
RPCExamples{
"Analyse a descriptor\n" +
HelpExampleCli("getdescriptorinfo", "\"wpkh([d34db33f/84h/0h/0h]0279be667ef9dcbbac55a06295Ce870b07029Bfcdb2dce28d959f2815b16f81798)\"")
- }}.Check(request);
-
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
RPCTypeCheck(request.params, {UniValue::VSTR});
FlatSigningProvider provider;
@@ -175,11 +180,13 @@ UniValue getdescriptorinfo(const JSONRPCRequest& request)
result.pushKV("issolvable", desc->IsSolvable());
result.pushKV("hasprivatekeys", provider.keys.size() > 0);
return result;
+},
+ };
}
-UniValue deriveaddresses(const JSONRPCRequest& request)
+static RPCHelpMan deriveaddresses()
{
- RPCHelpMan{"deriveaddresses",
+ return RPCHelpMan{"deriveaddresses",
{"\nDerives one or more addresses corresponding to an output descriptor.\n"
"Examples of output descriptors are:\n"
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
@@ -202,8 +209,9 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
RPCExamples{
"First three native segwit receive addresses\n" +
HelpExampleCli("deriveaddresses", "\"wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu\" \"[0,2]\"")
- }}.Check(request);
-
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // Range argument is checked later
const std::string desc_str = request.params[0].get_str();
@@ -254,11 +262,13 @@ UniValue deriveaddresses(const JSONRPCRequest& request)
}
return addresses;
+},
+ };
}
-static UniValue verifymessage(const JSONRPCRequest& request)
+static RPCHelpMan verifymessage()
{
- RPCHelpMan{"verifymessage",
+ return RPCHelpMan{"verifymessage",
"\nVerify a signed message\n",
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."},
@@ -278,8 +288,8 @@ static UniValue verifymessage(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"signature\", \"my message\"")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
LOCK(cs_main);
std::string strAddress = request.params[0].get_str();
@@ -301,11 +311,13 @@ static UniValue verifymessage(const JSONRPCRequest& request)
}
return false;
+},
+ };
}
-static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
+static RPCHelpMan signmessagewithprivkey()
{
- RPCHelpMan{"signmessagewithprivkey",
+ return RPCHelpMan{"signmessagewithprivkey",
"\nSign a message with the private key of an address\n",
{
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key to sign the message with."},
@@ -322,8 +334,8 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("signmessagewithprivkey", "\"privkey\", \"my message\"")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
std::string strPrivkey = request.params[0].get_str();
std::string strMessage = request.params[1].get_str();
@@ -339,11 +351,13 @@ static UniValue signmessagewithprivkey(const JSONRPCRequest& request)
}
return signature;
+},
+ };
}
-static UniValue setmocktime(const JSONRPCRequest& request)
+static RPCHelpMan setmocktime()
{
- RPCHelpMan{"setmocktime",
+ return RPCHelpMan{"setmocktime",
"\nSet the local time to given timestamp (-regtest only)\n",
{
{"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, UNIX_EPOCH_TIME + "\n"
@@ -351,8 +365,8 @@ static UniValue setmocktime(const JSONRPCRequest& request)
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
if (!Params().IsMockableChain()) {
throw std::runtime_error("setmocktime is for regression testing (-regtest mode) only");
}
@@ -374,19 +388,21 @@ static UniValue setmocktime(const JSONRPCRequest& request)
}
return NullUniValue;
+},
+ };
}
-static UniValue mockscheduler(const JSONRPCRequest& request)
+static RPCHelpMan mockscheduler()
{
- RPCHelpMan{"mockscheduler",
+ return RPCHelpMan{"mockscheduler",
"\nBump the scheduler into the future (-regtest only)\n",
{
{"delta_time", RPCArg::Type::NUM, RPCArg::Optional::NO, "Number of seconds to forward the scheduler into the future." },
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
if (!Params().IsMockableChain()) {
throw std::runtime_error("mockscheduler is for regression testing (-regtest mode) only");
}
@@ -405,6 +421,8 @@ static UniValue mockscheduler(const JSONRPCRequest& request)
node.scheduler->MockForward(std::chrono::seconds(delta_seconds));
return NullUniValue;
+},
+ };
}
static UniValue RPCLockedMemoryInfo()
@@ -439,12 +457,12 @@ static std::string RPCMallocInfo()
}
#endif
-static UniValue getmemoryinfo(const JSONRPCRequest& request)
+static RPCHelpMan getmemoryinfo()
{
/* Please, avoid using the word "pool" here in the RPC interface or help,
* as users will undoubtedly confuse it with the other "memory pool"
*/
- RPCHelpMan{"getmemoryinfo",
+ return RPCHelpMan{"getmemoryinfo",
"Returns an object containing information about memory usage.\n",
{
{"mode", RPCArg::Type::STR, /* default */ "\"stats\"", "determines what kind of information is returned.\n"
@@ -474,8 +492,8 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request)
HelpExampleCli("getmemoryinfo", "")
+ HelpExampleRpc("getmemoryinfo", "")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
std::string mode = request.params[0].isNull() ? "stats" : request.params[0].get_str();
if (mode == "stats") {
UniValue obj(UniValue::VOBJ);
@@ -490,6 +508,8 @@ static UniValue getmemoryinfo(const JSONRPCRequest& request)
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown mode " + mode);
}
+},
+ };
}
static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
@@ -510,9 +530,9 @@ static void EnableOrDisableLogCategories(UniValue cats, bool enable) {
}
}
-UniValue logging(const JSONRPCRequest& request)
+static RPCHelpMan logging()
{
- RPCHelpMan{"logging",
+ return RPCHelpMan{"logging",
"Gets and sets the logging configuration.\n"
"When called without an argument, returns the list of categories with status that are currently being debug logged or not.\n"
"When called with arguments, adds or removes categories from debug logging and return the lists above.\n"
@@ -543,8 +563,8 @@ UniValue logging(const JSONRPCRequest& request)
HelpExampleCli("logging", "\"[\\\"all\\\"]\" \"[\\\"http\\\"]\"")
+ HelpExampleRpc("logging", "[\"all\"], [\"libevent\"]")
},
- }.Check(request);
-
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
uint32_t original_log_categories = LogInstance().GetCategoryMask();
if (request.params[0].isArray()) {
EnableOrDisableLogCategories(request.params[0], true);
@@ -575,28 +595,47 @@ UniValue logging(const JSONRPCRequest& request)
}
return result;
+},
+ };
}
-static UniValue echo(const JSONRPCRequest& request)
+static RPCHelpMan echo(const std::string& name)
{
- if (request.fHelp)
- throw std::runtime_error(
- RPCHelpMan{"echo|echojson ...",
+ return RPCHelpMan{name,
"\nSimply echo back the input arguments. This command is for testing.\n"
- "\nIt will return an internal bug report when exactly 100 arguments are passed.\n"
+ "\nIt will return an internal bug report when arg9='trigger_internal_bug' is passed.\n"
"\nThe difference between echo and echojson is that echojson has argument conversion enabled in the client-side table in "
"bitcoin-cli and the GUI. There is no server-side difference.",
- {},
+ {
+ {"arg0", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg1", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg2", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg3", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg4", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg5", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg6", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg7", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg8", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ {"arg9", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, ""},
+ },
RPCResult{RPCResult::Type::NONE, "", "Returns whatever was passed in"},
RPCExamples{""},
- }.ToString()
- );
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ if (request.fHelp) throw std::runtime_error(self.ToString());
- CHECK_NONFATAL(request.params.size() != 100);
+ if (request.params[9].isStr()) {
+ CHECK_NONFATAL(request.params[9].get_str() != "trigger_internal_bug");
+ }
return request.params;
+},
+ };
}
+static RPCHelpMan echo() { return echo("echo"); }
+static RPCHelpMan echojson() { return echo("echojson"); }
+
void RegisterMiscRPCCommands(CRPCTable &t)
{
// clang-format off
@@ -616,10 +655,10 @@ static const CRPCCommand commands[] =
{ "hidden", "setmocktime", &setmocktime, {"timestamp"}},
{ "hidden", "mockscheduler", &mockscheduler, {"delta_time"}},
{ "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
- { "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
+ { "hidden", "echojson", &echojson, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
};
// clang-format on
-
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- t.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
}
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 77d24a6e79..9bd7c15992 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -843,7 +843,7 @@ static const CRPCCommand commands[] =
{ "hidden", "addpeeraddress", &addpeeraddress, {"address", "port"} },
};
// clang-format on
-
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- t.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
}
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 9b6ef15785..abc8168c55 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -744,7 +744,7 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
- {RPCResult::Type::ARR, "errors", "Script verification errors (if there are any)",
+ {RPCResult::Type::ARR, "errors", /* optional */ true, "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
{
@@ -1722,7 +1722,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request)
{RPCResult::Type::STR_AMOUNT, "estimated_feerate", /* optional */ true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"},
- {RPCResult::Type::STR, "error", "Error message if there is one"},
+ {RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"},
}
},
RPCExamples {
@@ -1821,7 +1821,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },
};
// clang-format on
-
- for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++)
- t.appendCommand(commands[vcidx].name, &commands[vcidx]);
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
}
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index e5f6b1b9f1..9c8e7fe04a 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -256,13 +256,8 @@ static const CRPCCommand vRPCCommands[] =
CRPCTable::CRPCTable()
{
- unsigned int vcidx;
- for (vcidx = 0; vcidx < (sizeof(vRPCCommands) / sizeof(vRPCCommands[0])); vcidx++)
- {
- const CRPCCommand *pcmd;
-
- pcmd = &vRPCCommands[vcidx];
- mapCommands[pcmd->name].push_back(pcmd);
+ for (const auto& c : vRPCCommands) {
+ appendCommand(c.name, &c);
}
}
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 073a7688a9..40dfdb587e 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -504,7 +504,7 @@ std::string RPCHelpMan::ToString() const
ret += m_name;
bool was_optional{false};
for (const auto& arg : m_args) {
- if (arg.m_hidden) continue;
+ if (arg.m_hidden) break; // Any arg that follows is also hidden
const bool optional = arg.IsOptional();
ret += " ";
if (optional) {
@@ -526,7 +526,7 @@ std::string RPCHelpMan::ToString() const
Sections sections;
for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg = m_args.at(i);
- if (arg.m_hidden) continue;
+ if (arg.m_hidden) break; // Any arg that follows is also hidden
if (i == 0) ret += "\nArguments:\n";
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index f425215549..9b3f94f14d 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -186,6 +186,8 @@ static CScript PushAll(const std::vector<valtype>& values)
result << OP_0;
} else if (v.size() == 1 && v[0] >= 1 && v[0] <= 16) {
result << CScript::EncodeOP_N(v[0]);
+ } else if (v.size() == 1 && v[0] == 0x81) {
+ result << OP_1NEGATE;
} else {
result << v;
}
diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp
index 9a9686f016..94b5dba913 100644
--- a/src/test/transaction_tests.cpp
+++ b/src/test/transaction_tests.cpp
@@ -361,6 +361,8 @@ static CScript PushAll(const std::vector<valtype>& values)
result << OP_0;
} else if (v.size() == 1 && v[0] >= 1 && v[0] <= 16) {
result << CScript::EncodeOP_N(v[0]);
+ } else if (v.size() == 1 && v[0] == 0x81) {
+ result << OP_1NEGATE;
} else {
result << v;
}
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index b49370c967..bf7c6c3e3e 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -550,57 +550,52 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3);
BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2);
- BOOST_CHECK(test_args.m_settings.ro_config[""].count("a")
- && test_args.m_settings.ro_config[""].count("b")
- && test_args.m_settings.ro_config[""].count("ccc")
- && test_args.m_settings.ro_config[""].count("d")
- && test_args.m_settings.ro_config[""].count("fff")
- && test_args.m_settings.ro_config[""].count("ggg")
- && test_args.m_settings.ro_config[""].count("h")
- && test_args.m_settings.ro_config[""].count("i")
- );
- BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc")
- && test_args.m_settings.ro_config["sec1"].count("h")
- && test_args.m_settings.ro_config["sec2"].count("ccc")
- && test_args.m_settings.ro_config["sec2"].count("iii")
- );
-
- BOOST_CHECK(test_args.IsArgSet("-a")
- && test_args.IsArgSet("-b")
- && test_args.IsArgSet("-ccc")
- && test_args.IsArgSet("-d")
- && test_args.IsArgSet("-fff")
- && test_args.IsArgSet("-ggg")
- && test_args.IsArgSet("-h")
- && test_args.IsArgSet("-i")
- && !test_args.IsArgSet("-zzz")
- && !test_args.IsArgSet("-iii")
- );
-
- BOOST_CHECK(test_args.GetArg("-a", "xxx") == ""
- && test_args.GetArg("-b", "xxx") == "1"
- && test_args.GetArg("-ccc", "xxx") == "argument"
- && test_args.GetArg("-d", "xxx") == "e"
- && test_args.GetArg("-fff", "xxx") == "0"
- && test_args.GetArg("-ggg", "xxx") == "1"
- && test_args.GetArg("-h", "xxx") == "0"
- && test_args.GetArg("-i", "xxx") == "1"
- && test_args.GetArg("-zzz", "xxx") == "xxx"
- && test_args.GetArg("-iii", "xxx") == "xxx"
- );
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("a"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("b"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("d"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("fff"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("ggg"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("h"));
+ BOOST_CHECK(test_args.m_settings.ro_config[""].count("i"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("h"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("ccc"));
+ BOOST_CHECK(test_args.m_settings.ro_config["sec2"].count("iii"));
+
+ BOOST_CHECK(test_args.IsArgSet("-a"));
+ BOOST_CHECK(test_args.IsArgSet("-b"));
+ BOOST_CHECK(test_args.IsArgSet("-ccc"));
+ BOOST_CHECK(test_args.IsArgSet("-d"));
+ BOOST_CHECK(test_args.IsArgSet("-fff"));
+ BOOST_CHECK(test_args.IsArgSet("-ggg"));
+ BOOST_CHECK(test_args.IsArgSet("-h"));
+ BOOST_CHECK(test_args.IsArgSet("-i"));
+ BOOST_CHECK(!test_args.IsArgSet("-zzz"));
+ BOOST_CHECK(!test_args.IsArgSet("-iii"));
+
+ BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ccc", "xxx"), "argument");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-d", "xxx"), "e");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-h", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-i", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
for (const bool def : {false, true}) {
- BOOST_CHECK(test_args.GetBoolArg("-a", def)
- && test_args.GetBoolArg("-b", def)
- && !test_args.GetBoolArg("-ccc", def)
- && !test_args.GetBoolArg("-d", def)
- && !test_args.GetBoolArg("-fff", def)
- && test_args.GetBoolArg("-ggg", def)
- && !test_args.GetBoolArg("-h", def)
- && test_args.GetBoolArg("-i", def)
- && test_args.GetBoolArg("-zzz", def) == def
- && test_args.GetBoolArg("-iii", def) == def
- );
+ BOOST_CHECK(test_args.GetBoolArg("-a", def));
+ BOOST_CHECK(test_args.GetBoolArg("-b", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-ccc", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-d", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-fff", def));
+ BOOST_CHECK(test_args.GetBoolArg("-ggg", def));
+ BOOST_CHECK(!test_args.GetBoolArg("-h", def));
+ BOOST_CHECK(test_args.GetBoolArg("-i", def));
+ BOOST_CHECK(test_args.GetBoolArg("-zzz", def) == def);
+ BOOST_CHECK(test_args.GetBoolArg("-iii", def) == def);
}
BOOST_CHECK(test_args.GetArgs("-a").size() == 1
@@ -636,13 +631,12 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
test_args.SelectConfigNetwork("sec1");
// same as original
- BOOST_CHECK(test_args.GetArg("-a", "xxx") == ""
- && test_args.GetArg("-b", "xxx") == "1"
- && test_args.GetArg("-fff", "xxx") == "0"
- && test_args.GetArg("-ggg", "xxx") == "1"
- && test_args.GetArg("-zzz", "xxx") == "xxx"
- && test_args.GetArg("-iii", "xxx") == "xxx"
- );
+ BOOST_CHECK_EQUAL(test_args.GetArg("-a", "xxx"), "");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-b", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-fff", "xxx"), "0");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-ggg", "xxx"), "1");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-zzz", "xxx"), "xxx");
+ BOOST_CHECK_EQUAL(test_args.GetArg("-iii", "xxx"), "xxx");
// d is overridden
BOOST_CHECK(test_args.GetArg("-d", "xxx") == "eee");
// section-specific setting
@@ -657,14 +651,13 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream)
test_args.SelectConfigNetwork("sec2");
// same as original
- BOOST_CHECK(test_args.GetArg("-a", "xxx") == ""
- && test_args.GetArg("-b", "xxx") == "1"
- && test_args.GetArg("-d", "xxx") == "e"
- && test_args.GetArg("-fff", "xxx") == "0"
- && test_args.GetArg("-ggg", "xxx") == "1"
- && test_args.GetArg("-zzz", "xxx") == "xxx"
- && test_args.GetArg("-h", "xxx") == "0"
- );
+ BOOST_CHECK(test_args.GetArg("-a", "xxx") == "");
+ BOOST_CHECK(test_args.GetArg("-b", "xxx") == "1");
+ BOOST_CHECK(test_args.GetArg("-d", "xxx") == "e");
+ BOOST_CHECK(test_args.GetArg("-fff", "xxx") == "0");
+ BOOST_CHECK(test_args.GetArg("-ggg", "xxx") == "1");
+ BOOST_CHECK(test_args.GetArg("-zzz", "xxx") == "xxx");
+ BOOST_CHECK(test_args.GetArg("-h", "xxx") == "0");
// section-specific setting
BOOST_CHECK(test_args.GetArg("-iii", "xxx") == "2");
// section takes priority for multiple values
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 079a5d3d53..1a45a2b313 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -5,6 +5,7 @@
#include <wallet/coinselection.h>
#include <optional.h>
+#include <policy/feerate.h>
#include <util/system.h>
#include <util/moneystr.h>
@@ -302,7 +303,7 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
m_outputs.push_back(output);
m_from_me &= from_me;
- m_value += output.effective_value;
+ m_value += output.txout.nValue;
m_depth = std::min(m_depth, depth);
// ancestors here express the number of ancestors the new coin will end up having, which is
// the sum, rather than the max; this will overestimate in the cases where multiple inputs
@@ -311,15 +312,19 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
// coin itself; thus, this value is counted as the max, not the sum
m_descendants = std::max(m_descendants, descendants);
- effective_value = m_value;
+ effective_value += output.effective_value;
+ fee += output.m_fee;
+ long_term_fee += output.m_long_term_fee;
}
std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
auto it = m_outputs.begin();
while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
if (it == m_outputs.end()) return it;
- m_value -= output.effective_value;
+ m_value -= output.txout.nValue;
effective_value -= output.effective_value;
+ fee -= output.m_fee;
+ long_term_fee -= output.m_long_term_fee;
return m_outputs.erase(it);
}
@@ -329,3 +334,35 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
&& m_ancestors <= eligibility_filter.max_ancestors
&& m_descendants <= eligibility_filter.max_descendants;
}
+
+void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
+{
+ fee = 0;
+ long_term_fee = 0;
+ effective_value = 0;
+ for (CInputCoin& coin : m_outputs) {
+ coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
+ fee += coin.m_fee;
+
+ coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
+ long_term_fee += coin.m_long_term_fee;
+
+ coin.effective_value = coin.txout.nValue - coin.m_fee;
+ effective_value += coin.effective_value;
+ }
+}
+
+OutputGroup OutputGroup::GetPositiveOnlyGroup()
+{
+ OutputGroup group(*this);
+ for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
+ const CInputCoin& coin = *it;
+ // Only include outputs that are positive effective value (i.e. not dust)
+ if (coin.effective_value <= 0) {
+ it = group.Discard(coin);
+ } else {
+ ++it;
+ }
+ }
+ return group;
+}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 5348401f45..49c1134ec6 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -9,6 +9,8 @@
#include <primitives/transaction.h>
#include <random.h>
+class CFeeRate;
+
//! target minimum change amount
static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees
@@ -36,6 +38,8 @@ public:
COutPoint outpoint;
CTxOut txout;
CAmount effective_value;
+ CAmount m_fee{0};
+ CAmount m_long_term_fee{0};
/** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */
int m_input_bytes{-1};
@@ -91,6 +95,10 @@ struct OutputGroup
void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
+
+ //! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
+ void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
+ OutputGroup GetPositiveOnlyGroup();
};
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 52162ab521..4c1fe57c66 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -9,6 +9,7 @@
#include <node/context.h>
#include <node/ui_interface.h>
#include <outputtype.h>
+#include <univalue.h>
#include <util/check.h>
#include <util/moneystr.h>
#include <util/system.h>
@@ -118,6 +119,14 @@ void WalletInit::Construct(NodeContext& node) const
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;
+ });
+ }
node.chain_clients.emplace_back(interfaces::MakeWalletClient(*node.chain, args, args.GetArgs("-wallet")));
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 2a81d30133..ae14769edb 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -13,6 +13,8 @@
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
+#include <univalue.h>
+
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files)
{
if (gArgs.IsArgSet("-walletdir")) {
@@ -120,3 +122,26 @@ void UnloadWallets()
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 ff4f5b4b23..30f1a4c90d 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -34,4 +34,10 @@ 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 17460a2932..7a03c4f544 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -30,6 +30,7 @@
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/feebumper.h>
+#include <wallet/load.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
@@ -229,6 +230,18 @@ static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const U
}
}
+static void UpdateWalletSetting(interfaces::Chain& chain,
+ const std::string& wallet_name,
+ const UniValue& load_on_startup,
+ std::vector<bilingual_str>& warnings)
+{
+ if (load_on_startup.isTrue() && !AddWalletSetting(chain, wallet_name)) {
+ warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may not be loaded next node startup."));
+ } else if (load_on_startup.isFalse() && !RemoveWalletSetting(chain, wallet_name)) {
+ warnings.emplace_back(Untranslated("Wallet load on startup setting could not be updated, so wallet may still be loaded next node startup."));
+ }
+}
+
static UniValue getnewaddress(const JSONRPCRequest& request)
{
RPCHelpMan{"getnewaddress",
@@ -1484,7 +1497,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
{RPCResult::Type::ARR, "removed", "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
"Note: transactions that were re-added in the active chain will appear as-is in this array, and may thus have a positive confirmation count."
, {{RPCResult::Type::ELISION, "", ""},}},
- {RPCResult::Type::STR_HEX, "lastblock", "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"},
+ {RPCResult::Type::STR_HEX, "lastblock", "The hash of the block (target_confirmations-1) from the best block on the main chain, or the genesis hash if the referenced block does not exist yet. 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"},
}
},
RPCExamples{
@@ -1567,6 +1580,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
}
uint256 lastblock;
+ target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
UniValue ret(UniValue::VOBJ);
@@ -2483,6 +2497,7 @@ static UniValue loadwallet(const JSONRPCRequest& request)
"\napplied to the new wallet (eg -zapwallettxes, rescan, etc).\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2515,6 +2530,8 @@ static UniValue loadwallet(const JSONRPCRequest& request)
std::shared_ptr<CWallet> const wallet = LoadWallet(*context.chain, location, error, warnings);
if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original);
+ UpdateWalletSetting(*context.chain, location.GetName(), request.params[1], warnings);
+
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2599,6 +2616,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."},
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
{"descriptors", RPCArg::Type::BOOL, /* default */ "false", "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2654,6 +2672,8 @@ static UniValue createwallet(const JSONRPCRequest& request)
// no default case, so the compiler can warn about missing cases
}
+ UpdateWalletSetting(*context.chain, request.params[0].get_str(), request.params[6], warnings);
+
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
@@ -2668,8 +2688,11 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
"Specifying the wallet name on a wallet endpoint is invalid.",
{
{"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."},
+ {"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
},
- RPCResult{RPCResult::Type::NONE, "", ""},
+ RPCResult{RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
+ }},
RPCExamples{
HelpExampleCli("unloadwallet", "wallet_name")
+ HelpExampleRpc("unloadwallet", "wallet_name")
@@ -2697,9 +2720,15 @@ static UniValue unloadwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
+ interfaces::Chain& chain = wallet->chain();
+ std::vector<bilingual_str> warnings;
+
UnloadWallet(std::move(wallet));
+ UpdateWalletSetting(chain, wallet_name, request.params[1], warnings);
- return NullUniValue;
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
+ return result;
}
static UniValue listunspent(const JSONRPCRequest& request)
@@ -3165,7 +3194,7 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
- {RPCResult::Type::ARR, "errors", "Script verification errors (if there are any)",
+ {RPCResult::Type::ARR, "errors", /* optional */ true, "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
{
@@ -4157,7 +4186,7 @@ static const CRPCCommand commands[] =
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
@@ -4190,7 +4219,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "listwalletdir", &listwalletdir, {} },
{ "wallet", "listwallets", &listwallets, {} },
- { "wallet", "loadwallet", &loadwallet, {"filename"} },
+ { "wallet", "loadwallet", &loadwallet, {"filename", "load_on_startup"} },
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
@@ -4202,7 +4231,7 @@ static const CRPCCommand commands[] =
{ "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
{ "wallet", "signmessage", &signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
- { "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
+ { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} },
{ "wallet", "upgradewallet", &upgradewallet, {"version"} },
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
{ "wallet", "walletlock", &walletlock, {} },
diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp
index af57210f01..c0755db751 100644
--- a/src/wallet/salvage.cpp
+++ b/src/wallet/salvage.cpp
@@ -16,14 +16,12 @@ static const char *HEADER_END = "HEADER=END";
static const char *DATA_END = "DATA=END";
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
-bool RecoverDatabaseFile(const fs::path& file_path)
+bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
- bilingual_str open_err;
- if (!env->Open(open_err)) {
- tfm::format(std::cerr, "%s\n", open_err.original);
+ if (!env->Open(error)) {
return false;
}
@@ -39,11 +37,9 @@ bool RecoverDatabaseFile(const fs::path& file_path)
int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
newFilename.c_str(), DB_AUTO_COMMIT);
- if (result == 0)
- LogPrintf("Renamed %s to %s\n", filename, newFilename);
- else
+ if (result != 0)
{
- LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
+ error = strprintf(Untranslated("Failed to rename %s to %s"), filename, newFilename);
return false;
}
@@ -60,10 +56,10 @@ bool RecoverDatabaseFile(const fs::path& file_path)
Db db(env->dbenv.get(), 0);
result = db.verify(newFilename.c_str(), nullptr, &strDump, DB_SALVAGE | DB_AGGRESSIVE);
if (result == DB_VERIFY_BAD) {
- LogPrintf("Salvage: Database salvage found errors, all data may not be recoverable.\n");
+ warnings.push_back(Untranslated("Salvage: Database salvage found errors, all data may not be recoverable."));
}
if (result != 0 && result != DB_VERIFY_BAD) {
- LogPrintf("Salvage: Database salvage failed with result %d.\n", result);
+ error = strprintf(Untranslated("Salvage: Database salvage failed with result %d."), result);
return false;
}
@@ -87,7 +83,7 @@ bool RecoverDatabaseFile(const fs::path& file_path)
break;
getline(strDump, valueHex);
if (valueHex == DATA_END) {
- LogPrintf("Salvage: WARNING: Number of keys in data does not match number of values.\n");
+ warnings.push_back(Untranslated("Salvage: WARNING: Number of keys in data does not match number of values."));
break;
}
salvagedData.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
@@ -96,7 +92,7 @@ bool RecoverDatabaseFile(const fs::path& file_path)
bool fSuccess;
if (keyHex != DATA_END) {
- LogPrintf("Salvage: WARNING: Unexpected end of file while reading salvage output.\n");
+ warnings.push_back(Untranslated("Salvage: WARNING: Unexpected end of file while reading salvage output."));
fSuccess = false;
} else {
fSuccess = (result == 0);
@@ -104,10 +100,9 @@ bool RecoverDatabaseFile(const fs::path& file_path)
if (salvagedData.empty())
{
- LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
+ error = strprintf(Untranslated("Salvage(aggressive) found no records in %s."), newFilename);
return false;
}
- LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
@@ -117,7 +112,7 @@ bool RecoverDatabaseFile(const fs::path& file_path)
DB_CREATE, // Flags
0);
if (ret > 0) {
- LogPrintf("Cannot create database file %s\n", filename);
+ error = strprintf(Untranslated("Cannot create database file %s"), filename);
pdbCopy->close(0);
return false;
}
@@ -141,7 +136,7 @@ bool RecoverDatabaseFile(const fs::path& file_path)
}
if (!fReadOK)
{
- LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType, strErr);
+ warnings.push_back(strprintf(Untranslated("WARNING: WalletBatch::Recover skipping %s: %s"), strType, strErr));
continue;
}
Dbt datKey(&row.first[0], row.first.size());
diff --git a/src/wallet/salvage.h b/src/wallet/salvage.h
index e361930f5e..5a8538f942 100644
--- a/src/wallet/salvage.h
+++ b/src/wallet/salvage.h
@@ -9,6 +9,8 @@
#include <fs.h>
#include <streams.h>
-bool RecoverDatabaseFile(const fs::path& file_path);
+struct bilingual_str;
+
+bool RecoverDatabaseFile(const fs::path& file_path, bilingual_str& error, std::vector<bilingual_str>& warnings);
#endif // BITCOIN_WALLET_SALVAGE_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index cee2f2214c..9c7b446c23 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2320,27 +2320,15 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
for (OutputGroup& group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) continue;
- group.fee = 0;
- group.long_term_fee = 0;
- group.effective_value = 0;
- for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
- const CInputCoin& coin = *it;
- CAmount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes));
- // Only include outputs that are positive effective value (i.e. not dust)
- if (effective_value > 0) {
- group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
- group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
- if (coin_selection_params.m_subtract_fee_outputs) {
- group.effective_value += coin.txout.nValue;
- } else {
- group.effective_value += effective_value;
- }
- ++it;
- } else {
- it = group.Discard(coin);
- }
+ if (coin_selection_params.m_subtract_fee_outputs) {
+ // Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
+ group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
+ } else {
+ group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
}
- if (group.effective_value > 0) utxo_pool.push_back(group);
+
+ OutputGroup pos_group = group.GetPositiveOnlyGroup();
+ if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
}
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
@@ -2486,23 +2474,6 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
}
// At this point, one input was not fully signed otherwise we would have exited already
- // Find that input and figure out what went wrong.
- for (unsigned int i = 0; i < tx.vin.size(); i++) {
- // Get the prevout
- CTxIn& txin = tx.vin[i];
- auto coin = coins.find(txin.prevout);
- if (coin == coins.end() || coin->second.IsSpent()) {
- input_errors[i] = "Input not found or already spent";
- continue;
- }
-
- // Check if this input is complete
- SignatureData sigdata = DataFromTransaction(tx, i, coin->second.out);
- if (!sigdata.complete) {
- input_errors[i] = "Unable to sign input, missing keys";
- continue;
- }
- }
return false;
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 7c5bf7652b..64d60b1f44 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -261,10 +261,6 @@ public:
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
- /* verifies the database environment */
- static bool VerifyEnvironment(const fs::path& wallet_path, bilingual_str& errorStr);
- /* verifies the database file */
- static bool VerifyDatabaseFile(const fs::path& wallet_path, bilingual_str& errorStr);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 9f25b1ae7d..c1cba0fd13 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -104,27 +104,6 @@ static void WalletShowInfo(CWallet* wallet_instance)
tfm::format(std::cout, "Address Book: %zu\n", wallet_instance->m_address_book.size());
}
-static bool SalvageWallet(const fs::path& path)
-{
- // Create a Database handle to allow for the db to be initialized before recovery
- std::unique_ptr<WalletDatabase> database = CreateWalletDatabase(path);
-
- // Initialize the environment before recovery
- bilingual_str error_string;
- try {
- database->Verify(error_string);
- } catch (const fs::filesystem_error& e) {
- error_string = Untranslated(strprintf("Error loading wallet. %s", fsbridge::get_filesystem_error_message(e)));
- }
- if (!error_string.original.empty()) {
- tfm::format(std::cerr, "Failed to open wallet for salvage :%s\n", error_string.original);
- return false;
- }
-
- // Perform the recovery
- return RecoverDatabaseFile(path);
-}
-
bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
{
fs::path path = fs::absolute(name, GetWalletDir());
@@ -147,7 +126,18 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name)
WalletShowInfo(wallet_instance.get());
wallet_instance->Close();
} else if (command == "salvage") {
- return SalvageWallet(path);
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+ bool ret = RecoverDatabaseFile(path, error, warnings);
+ if (!ret) {
+ for (const auto warning : warnings) {
+ tfm::format(std::cerr, "%s\n", warning.original);
+ }
+ if (!error.empty()) {
+ tfm::format(std::cerr, "%s\n", error.original);
+ }
+ }
+ return ret;
}
} else {
tfm::format(std::cerr, "Invalid command: %s\n", command);