aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am1
-rw-r--r--src/Makefile.qt.include4
-rw-r--r--src/bitcoin-cli.cpp2
-rw-r--r--src/bitcoin-tx.cpp2
-rw-r--r--src/bitcoin-wallet.cpp2
-rw-r--r--src/bitcoind.cpp28
-rw-r--r--src/chainparamsbase.cpp5
-rw-r--r--src/compat/glibc_sanity.cpp19
-rw-r--r--src/compat/glibc_sanity_fdelt.cpp26
-rw-r--r--src/consensus/tx_check.cpp2
-rw-r--r--src/core_write.cpp4
-rw-r--r--src/dummywallet.cpp7
-rw-r--r--src/init.cpp11
-rw-r--r--src/interfaces/node.cpp10
-rw-r--r--src/interfaces/node.h8
-rw-r--r--src/net.cpp135
-rw-r--r--src/net.h144
-rw-r--r--src/net_processing.cpp336
-rw-r--r--src/qt/askpassphrasedialog.cpp52
-rw-r--r--src/qt/askpassphrasedialog.h5
-rw-r--r--src/qt/bitcoin.cpp19
-rw-r--r--src/qt/bitcoin.h2
-rw-r--r--src/qt/bitcoingui.cpp53
-rw-r--r--src/qt/bitcoingui.h1
-rw-r--r--src/qt/bitcoinstrings.cpp2
-rw-r--r--src/qt/createwalletdialog.cpp62
-rw-r--r--src/qt/createwalletdialog.h35
-rw-r--r--src/qt/forms/askpassphrasedialog.ui2
-rw-r--r--src/qt/forms/createwalletdialog.ui151
-rw-r--r--src/qt/forms/intro.ui10
-rw-r--r--src/qt/forms/receivecoinsdialog.ui8
-rw-r--r--src/qt/guiconstants.h2
-rw-r--r--src/qt/guiutil.cpp4
-rw-r--r--src/qt/intro.cpp18
-rw-r--r--src/qt/intro.h3
-rw-r--r--src/qt/locale/bitcoin_en.ts321
-rw-r--r--src/qt/networkstyle.cpp15
-rw-r--r--src/qt/networkstyle.h2
-rw-r--r--src/qt/optionsmodel.cpp22
-rw-r--r--src/qt/optionsmodel.h3
-rw-r--r--src/qt/paymentserver.cpp8
-rw-r--r--src/qt/receivecoinsdialog.cpp8
-rw-r--r--src/qt/sendcoinsdialog.cpp4
-rw-r--r--src/qt/test/apptests.cpp3
-rw-r--r--src/qt/walletcontroller.cpp177
-rw-r--r--src/qt/walletcontroller.h79
-rw-r--r--src/qt/walletview.cpp4
-rw-r--r--src/rpc/blockchain.cpp33
-rw-r--r--src/rpc/client.cpp2
-rw-r--r--src/rpc/net.cpp16
-rw-r--r--src/rpc/rawtransaction.cpp67
-rw-r--r--src/rpc/rawtransaction_util.cpp82
-rw-r--r--src/rpc/rawtransaction_util.h16
-rw-r--r--src/rpc/server.cpp4
-rw-r--r--src/rpc/util.cpp18
-rw-r--r--src/rpc/util.h4
-rw-r--r--src/script/interpreter.cpp4
-rw-r--r--src/streams.h24
-rw-r--r--src/test/data/script_tests.json7
-rw-r--r--src/test/denialofservice_tests.cpp12
-rw-r--r--src/test/merkle_tests.cpp100
-rw-r--r--src/test/streams_tests.cpp244
-rw-r--r--src/test/util_tests.cpp2
-rw-r--r--src/util/system.cpp12
-rw-r--r--src/validation.cpp871
-rw-r--r--src/validation.h37
-rw-r--r--src/wallet/load.h2
-rw-r--r--src/wallet/rpcwallet.cpp20
-rw-r--r--src/wallet/wallet.cpp2
-rw-r--r--src/walletinitinterface.h4
70 files changed, 2432 insertions, 972 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 8fc7f61d4b..1ef62a656d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -484,6 +484,7 @@ libbitcoin_util_a_SOURCES = \
support/lockedpool.cpp \
chainparamsbase.cpp \
clientversion.cpp \
+ compat/glibc_sanity_fdelt.cpp \
compat/glibc_sanity.cpp \
compat/glibcxx_sanity.cpp \
compat/strnlen.cpp \
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include
index 6d8faf3883..7540122418 100644
--- a/src/Makefile.qt.include
+++ b/src/Makefile.qt.include
@@ -98,6 +98,7 @@ QT_FORMS_UI = \
qt/forms/addressbookpage.ui \
qt/forms/askpassphrasedialog.ui \
qt/forms/coincontroldialog.ui \
+ qt/forms/createwalletdialog.ui \
qt/forms/editaddressdialog.ui \
qt/forms/helpmessagedialog.ui \
qt/forms/intro.ui \
@@ -117,6 +118,7 @@ QT_MOC_CPP = \
qt/moc_addressbookpage.cpp \
qt/moc_addresstablemodel.cpp \
qt/moc_askpassphrasedialog.cpp \
+ qt/moc_createwalletdialog.cpp \
qt/moc_bantablemodel.cpp \
qt/moc_bitcoinaddressvalidator.cpp \
qt/moc_bitcoinamountfield.cpp \
@@ -202,6 +204,7 @@ BITCOIN_QT_H = \
qt/clientmodel.h \
qt/coincontroldialog.h \
qt/coincontroltreewidget.h \
+ qt/createwalletdialog.h \
qt/csvmodelwriter.h \
qt/editaddressdialog.h \
qt/guiconstants.h \
@@ -328,6 +331,7 @@ BITCOIN_QT_WALLET_CPP = \
qt/askpassphrasedialog.cpp \
qt/coincontroldialog.cpp \
qt/coincontroltreewidget.cpp \
+ qt/createwalletdialog.cpp \
qt/editaddressdialog.cpp \
qt/openuridialog.cpp \
qt/overviewpage.cpp \
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index cde624ce74..a6756fcce7 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -133,7 +133,7 @@ static int AppInitRPC(int argc, char* argv[])
tfm::format(std::cerr, "Error reading configuration file: %s\n", error.c_str());
return EXIT_FAILURE;
}
- // Check for -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
+ // Check for -chain, -testnet or -regtest parameter (BaseParams() calls are only valid after this clause)
try {
SelectBaseParams(gArgs.GetChainName());
} catch (const std::exception& e) {
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp
index f4972c3cd4..88219f0d0f 100644
--- a/src/bitcoin-tx.cpp
+++ b/src/bitcoin-tx.cpp
@@ -88,7 +88,7 @@ static int AppInitRawTx(int argc, char* argv[])
return EXIT_FAILURE;
}
- // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
+ // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {
SelectParams(gArgs.GetChainName());
} catch (const std::exception& e) {
diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp
index 361fedf35a..eb7f0098ec 100644
--- a/src/bitcoin-wallet.cpp
+++ b/src/bitcoin-wallet.cpp
@@ -27,7 +27,7 @@ static void SetupWalletToolArgs()
gArgs.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-wallet=<wallet-name>", "Specify wallet name", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::OPTIONS);
gArgs.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
- gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise.", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
+ gArgs.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("info", "Get wallet info", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
gArgs.AddArg("create", "Create new wallet file", ArgsManager::ALLOW_ANY, OptionsCategory::COMMANDS);
diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp
index cb3c4f70b4..615b955f6e 100644
--- a/src/bitcoind.cpp
+++ b/src/bitcoind.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -25,24 +25,6 @@
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
-/* Introduction text for doxygen: */
-
-/*! \mainpage Developer documentation
- *
- * \section intro_sec Introduction
- *
- * This is the developer documentation of the reference client for an experimental new digital currency called Bitcoin,
- * which enables instant payments to anyone, anywhere in the world. Bitcoin uses peer-to-peer technology to operate
- * with no central authority: managing transactions and issuing money are carried out collectively by the network.
- *
- * The software is a community-driven open source project, released under the MIT license.
- *
- * See https://github.com/bitcoin/bitcoin and https://bitcoincore.org/ for further information about the project.
- *
- * \section Navigation
- * Use the buttons <code>Namespaces</code>, <code>Classes</code> or <code>Files</code> at the top of the page to start navigating the code.
- */
-
static void WaitForShutdown()
{
while (!ShutdownRequested())
@@ -77,7 +59,7 @@ static bool AppInit(int argc, char* argv[])
// Process help and version before taking care about datadir
if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) {
- std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n";
+ std::string strUsage = PACKAGE_NAME " version " + FormatFullVersion() + "\n";
if (gArgs.IsArgSet("-version"))
{
@@ -85,7 +67,7 @@ static bool AppInit(int argc, char* argv[])
}
else
{
- strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME " Daemon\n";
+ strUsage += "\nUsage: bitcoind [options] Start " PACKAGE_NAME "\n";
strUsage += "\n" + gArgs.GetHelpMessage();
}
@@ -101,7 +83,7 @@ static bool AppInit(int argc, char* argv[])
if (!gArgs.ReadConfigFiles(error, true)) {
return InitError(strprintf("Error reading configuration file: %s\n", error));
}
- // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
+ // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {
SelectParams(gArgs.GetChainName());
} catch (const std::exception& e) {
@@ -142,7 +124,7 @@ static bool AppInit(int argc, char* argv[])
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
- tfm::format(std::cout, PACKAGE_NAME " daemon starting\n");
+ tfm::format(std::cout, PACKAGE_NAME " starting\n");
// Daemonize
if (daemon(1, 0)) { // don't chdir (1), do close FDs (0)
diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp
index 9b98dff3ca..4bb66c8d8b 100644
--- a/src/chainparamsbase.cpp
+++ b/src/chainparamsbase.cpp
@@ -17,10 +17,11 @@ const std::string CBaseChainParams::REGTEST = "regtest";
void SetupChainParamsBaseOptions()
{
+ gArgs.AddArg("-chain=<chain>", "Use the chain <chain> (default: main). Allowed values: main, test, regtest", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. "
- "This is intended for regression testing tools and app development.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
+ "This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
- gArgs.AddArg("-testnet", "Use the test chain", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
+ gArgs.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
gArgs.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
}
diff --git a/src/compat/glibc_sanity.cpp b/src/compat/glibc_sanity.cpp
index 1ef66e27b4..cc74f28899 100644
--- a/src/compat/glibc_sanity.cpp
+++ b/src/compat/glibc_sanity.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2018 The Bitcoin Core developers
+// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -9,7 +9,7 @@
#include <cstddef>
#if defined(HAVE_SYS_SELECT_H)
-#include <sys/select.h>
+bool sanity_test_fdelt();
#endif
extern "C" void* memcpy(void* a, const void* b, size_t c);
@@ -41,21 +41,6 @@ bool sanity_test_memcpy()
}
return true;
}
-
-#if defined(HAVE_SYS_SELECT_H)
-// trigger: Call FD_SET to trigger __fdelt_chk. FORTIFY_SOURCE must be defined
-// as >0 and optimizations must be set to at least -O2.
-// test: Add a file descriptor to an empty fd_set. Verify that it has been
-// correctly added.
-bool sanity_test_fdelt()
-{
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(0, &fds);
- return FD_ISSET(0, &fds);
-}
-#endif
-
} // namespace
bool glibc_sanity_test()
diff --git a/src/compat/glibc_sanity_fdelt.cpp b/src/compat/glibc_sanity_fdelt.cpp
new file mode 100644
index 0000000000..87140d0c71
--- /dev/null
+++ b/src/compat/glibc_sanity_fdelt.cpp
@@ -0,0 +1,26 @@
+// 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.
+
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
+#if defined(HAVE_SYS_SELECT_H)
+#ifdef HAVE_CSTRING_DEPENDENT_FD_ZERO
+#include <cstring>
+#endif
+#include <sys/select.h>
+
+// trigger: Call FD_SET to trigger __fdelt_chk. FORTIFY_SOURCE must be defined
+// as >0 and optimizations must be set to at least -O2.
+// test: Add a file descriptor to an empty fd_set. Verify that it has been
+// correctly added.
+bool sanity_test_fdelt()
+{
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(0, &fds);
+ return FD_ISSET(0, &fds);
+}
+#endif
diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp
index 23ed3ecb53..00ebbbd1ab 100644
--- a/src/consensus/tx_check.cpp
+++ b/src/consensus/tx_check.cpp
@@ -18,7 +18,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT)
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-oversize");
- // Check for negative or overflow output values
+ // Check for negative or overflow output values (see CVE-2010-5139)
CAmount nValueOut = 0;
for (const auto& txout : tx.vout)
{
diff --git a/src/core_write.cpp b/src/core_write.cpp
index 4d64446d7b..7ce2a49836 100644
--- a/src/core_write.cpp
+++ b/src/core_write.cpp
@@ -144,7 +144,7 @@ void ScriptToUniv(const CScript& script, UniValue& out, bool include_address)
out.pushKV("type", GetTxnOutputType(type));
CTxDestination address;
- if (include_address && ExtractDestination(script, address)) {
+ if (include_address && ExtractDestination(script, address) && type != TX_PUBKEY) {
out.pushKV("address", EncodeDestination(address));
}
}
@@ -160,7 +160,7 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey,
if (fIncludeHex)
out.pushKV("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
- if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired)) {
+ if (!ExtractDestinations(scriptPubKey, type, addresses, nRequired) || type == TX_PUBKEY) {
out.pushKV("type", GetTxnOutputType(type));
return;
}
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index eeec6dec25..126e3479f3 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -5,8 +5,10 @@
#include <stdio.h>
#include <util/system.h>
#include <walletinitinterface.h>
+#include <support/allocators/secure.h>
class CWallet;
+enum class WalletCreationStatus;
namespace interfaces {
class Chain;
@@ -74,6 +76,11 @@ std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string&
throw std::logic_error("Wallet function called in non-wallet build.");
}
+WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result)
+{
+ throw std::logic_error("Wallet function called in non-wallet build.");
+}
+
namespace interfaces {
class Wallet;
diff --git a/src/init.cpp b/src/init.cpp
index ca419c05fa..7c752d615a 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1106,7 +1106,7 @@ bool AppInitParameterInteraction()
if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) {
return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")).translated);
}
- // High fee check is done afterward in WalletParameterInteraction()
+ // High fee check is done afterward in CWallet::CreateWalletFromFile()
::minRelayTxFee = CFeeRate(n);
} else if (incrementalRelayFee > ::minRelayTxFee) {
// Allow only setting incrementalRelayFee to control both
@@ -1545,7 +1545,7 @@ bool AppInitMain(InitInterfaces& interfaces)
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb with -reindex or -reindex-chainstate
- if (!ReplayBlocks(chainparams, &::ChainstateActive().CoinsDB())) {
+ if (!::ChainstateActive().ReplayBlocks(chainparams)) {
strLoadError = _("Unable to replay blocks. You will need to rebuild the database using -reindex-chainstate.").translated;
break;
}
@@ -1557,8 +1557,8 @@ bool AppInitMain(InitInterfaces& interfaces)
is_coinsview_empty = fReset || fReindexChainState ||
::ChainstateActive().CoinsTip().GetBestBlock().IsNull();
if (!is_coinsview_empty) {
- // LoadChainTip sets ::ChainActive() based on CoinsTip()'s best block
- if (!LoadChainTip(chainparams)) {
+ // LoadChainTip initializes the chain based on CoinsTip()'s best block
+ if (!::ChainstateActive().LoadChainTip(chainparams)) {
strLoadError = _("Error initializing block database").translated;
break;
}
@@ -1760,7 +1760,8 @@ bool AppInitMain(InitInterfaces& interfaces)
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
- connOptions.nMaxOutbound = std::min(MAX_OUTBOUND_CONNECTIONS, connOptions.nMaxConnections);
+ connOptions.m_max_outbound_full_relay = std::min(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
+ connOptions.m_max_outbound_block_relay = std::min(MAX_BLOCKS_ONLY_CONNECTIONS, connOptions.nMaxConnections-connOptions.m_max_outbound_full_relay);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = 1;
connOptions.nBestHeight = chain_active_height;
diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp
index fc49817502..c80a8789fc 100644
--- a/src/interfaces/node.cpp
+++ b/src/interfaces/node.cpp
@@ -24,6 +24,7 @@
#include <primitives/block.h>
#include <rpc/server.h>
#include <shutdown.h>
+#include <support/allocators/secure.h>
#include <sync.h>
#include <txmempool.h>
#include <ui_interface.h>
@@ -43,6 +44,7 @@ fs::path GetWalletDir();
std::vector<fs::path> ListWalletDir();
std::vector<std::shared_ptr<CWallet>> GetWallets();
std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning);
+WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr<CWallet>& result);
namespace interfaces {
@@ -60,6 +62,7 @@ public:
return gArgs.ParseParameters(argc, argv, error);
}
bool readConfigFiles(std::string& error) override { return gArgs.ReadConfigFiles(error, true); }
+ void forceSetArg(const std::string& arg, const std::string& value) override { gArgs.ForceSetArg(arg, value); }
bool softSetArg(const std::string& arg, const std::string& value) override { return gArgs.SoftSetArg(arg, value); }
bool softSetBoolArg(const std::string& arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); }
void selectParams(const std::string& network) override { SelectParams(network); }
@@ -258,6 +261,13 @@ public:
{
return MakeWallet(LoadWallet(*m_interfaces.chain, name, error, warning));
}
+ WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) override
+ {
+ std::shared_ptr<CWallet> wallet;
+ WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warning, wallet);
+ result = MakeWallet(wallet);
+ return status;
+ }
std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override
{
return MakeHandler(::uiInterface.InitMessage_connect(fn));
diff --git a/src/interfaces/node.h b/src/interfaces/node.h
index b93b52c5cc..2f4f396e72 100644
--- a/src/interfaces/node.h
+++ b/src/interfaces/node.h
@@ -9,6 +9,7 @@
#include <amount.h> // For CAmount
#include <net.h> // For CConnman::NumConnections
#include <netaddress.h> // For Network
+#include <support/allocators/secure.h> // For SecureString
#include <functional>
#include <memory>
@@ -27,6 +28,7 @@ class RPCTimerInterface;
class UniValue;
class proxyType;
struct CNodeStateStats;
+enum class WalletCreationStatus;
namespace interfaces {
class Handler;
@@ -44,6 +46,9 @@ public:
//! Set command line arguments.
virtual bool parseParameters(int argc, const char* const argv[], std::string& error) = 0;
+ //! Set a command line argument
+ virtual void forceSetArg(const std::string& arg, const std::string& value) = 0;
+
//! Set a command line argument if it doesn't already have a value
virtual bool softSetArg(const std::string& arg, const std::string& value) = 0;
@@ -200,6 +205,9 @@ public:
//! with handleLoadWallet.
virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::string& warning) = 0;
+ //! Create a wallet from file
+ virtual WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr<Wallet>& result) = 0;
+
//! Register handler for init messages.
using InitMessageFn = std::function<void(const std::string& message)>;
virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0;
diff --git a/src/net.cpp b/src/net.cpp
index 337d1f6a46..63b7833822 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -50,6 +50,9 @@ static_assert(MINIUPNPC_API_VERSION >= 10, "miniUPnPc API version >= 10 assumed"
// Dump addresses to peers.dat every 15 minutes (900s)
static constexpr int DUMP_PEERS_INTERVAL = 15 * 60;
+/** Number of DNS seeds to query when the number of connections is low. */
+static constexpr int DNSSEEDS_TO_QUERY_AT_ONCE = 3;
+
// We add a random period time (0 to 1 seconds) to feeler connections to prevent synchronization.
#define FEELER_SLEEP_WINDOW 1
@@ -352,7 +355,7 @@ static CAddress GetBindAddress(SOCKET sock)
return addr_bind;
}
-CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection)
+CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only)
{
if (pszDest == nullptr) {
if (IsLocal(addrConnect))
@@ -442,7 +445,7 @@ CNode* CConnman::ConnectNode(CAddress addrConnect, const char *pszDest, bool fCo
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE).Write(id).Finalize();
CAddress addr_bind = GetBindAddress(hSocket);
- CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false);
+ CNode* pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket, addrConnect, CalculateKeyedNetGroup(addrConnect), nonce, addr_bind, pszDest ? pszDest : "", false, block_relay_only);
pnode->AddRef();
return pnode;
@@ -499,9 +502,11 @@ void CNode::copyStats(CNodeStats &stats)
X(nServices);
X(addr);
X(addrBind);
- {
- LOCK(cs_filter);
- X(fRelayTxes);
+ if (m_tx_relay != nullptr) {
+ LOCK(m_tx_relay->cs_filter);
+ stats.fRelayTxes = m_tx_relay->fRelayTxes;
+ } else {
+ stats.fRelayTxes = false;
}
X(nLastSend);
X(nLastRecv);
@@ -528,9 +533,11 @@ void CNode::copyStats(CNodeStats &stats)
}
X(m_legacyWhitelisted);
X(m_permissionFlags);
- {
- LOCK(cs_feeFilter);
- X(minFeeFilter);
+ if (m_tx_relay != nullptr) {
+ LOCK(m_tx_relay->cs_feeFilter);
+ stats.minFeeFilter = m_tx_relay->minFeeFilter;
+ } else {
+ stats.minFeeFilter = 0;
}
// It is common for nodes with good ping times to suddenly become lagged,
@@ -818,11 +825,17 @@ bool CConnman::AttemptToEvictConnection()
continue;
if (node->fDisconnect)
continue;
- LOCK(node->cs_filter);
+ bool peer_relay_txes = false;
+ bool peer_filter_not_null = false;
+ if (node->m_tx_relay != nullptr) {
+ LOCK(node->m_tx_relay->cs_filter);
+ peer_relay_txes = node->m_tx_relay->fRelayTxes;
+ peer_filter_not_null = node->m_tx_relay->pfilter != nullptr;
+ }
NodeEvictionCandidate candidate = {node->GetId(), node->nTimeConnected, node->nMinPingUsecTime,
node->nLastBlockTime, node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
- node->fRelayTxes, node->pfilter != nullptr, node->addr, node->nKeyedNetGroup,
+ peer_relay_txes, peer_filter_not_null, node->addr, node->nKeyedNetGroup,
node->m_prefer_evict};
vEvictionCandidates.push_back(candidate);
}
@@ -895,7 +908,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
CAddress addr;
int nInbound = 0;
- int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler);
+ int nMaxInbound = nMaxConnections - m_max_outbound;
if (hSocket != INVALID_SOCKET) {
if (!addr.SetSockAddr((const struct sockaddr*)&sockaddr)) {
@@ -1525,35 +1538,41 @@ void StopMapPort()
void CConnman::ThreadDNSAddressSeed()
{
- // goal: only query DNS seeds if address need is acute
- // Avoiding DNS seeds when we don't need them improves user privacy by
- // creating fewer identifying DNS requests, reduces trust by giving seeds
- // less influence on the network topology, and reduces traffic to the seeds.
- if ((addrman.size() > 0) &&
- (!gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) {
- if (!interruptNet.sleep_for(std::chrono::seconds(11)))
- return;
+ FastRandomContext rng;
+ std::vector<std::string> seeds = Params().DNSSeeds();
+ Shuffle(seeds.begin(), seeds.end(), rng);
+ int seeds_right_now = 0; // Number of seeds left before testing if we have enough connections
+ int found = 0;
- LOCK(cs_vNodes);
- int nRelevant = 0;
- for (const CNode* pnode : vNodes) {
- nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound;
- }
- if (nRelevant >= 2) {
- LogPrintf("P2P peers available. Skipped DNS seeding.\n");
- return;
- }
+ if (gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED)) {
+ // When -forcednsseed is provided, query all.
+ seeds_right_now = seeds.size();
}
- const std::vector<std::string> &vSeeds = Params().DNSSeeds();
- int found = 0;
+ for (const std::string& seed : seeds) {
+ // goal: only query DNS seed if address need is acute
+ // Avoiding DNS seeds when we don't need them improves user privacy by
+ // creating fewer identifying DNS requests, reduces trust by giving seeds
+ // less influence on the network topology, and reduces traffic to the seeds.
+ if (addrman.size() > 0 && seeds_right_now == 0) {
+ if (!interruptNet.sleep_for(std::chrono::seconds(11))) return;
- LogPrintf("Loading addresses from DNS seeds (could take a while)\n");
+ LOCK(cs_vNodes);
+ int nRelevant = 0;
+ for (const CNode* pnode : vNodes) {
+ nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler && !pnode->fOneShot && !pnode->m_manual_connection && !pnode->fInbound;
+ }
+ if (nRelevant >= 2) {
+ LogPrintf("P2P peers available. Skipped DNS seeding.\n");
+ return;
+ }
+ seeds_right_now += DNSSEEDS_TO_QUERY_AT_ONCE;
+ }
- for (const std::string &seed : vSeeds) {
if (interruptNet) {
return;
}
+ LogPrintf("Loading addresses from DNS seed %s\n", seed);
if (HaveNameProxy()) {
AddOneShot(seed);
} else {
@@ -1566,13 +1585,11 @@ void CConnman::ThreadDNSAddressSeed()
continue;
}
unsigned int nMaxIPs = 256; // Limits number of IPs learned from a DNS seed
- if (LookupHost(host.c_str(), vIPs, nMaxIPs, true))
- {
- for (const CNetAddr& ip : vIPs)
- {
+ if (LookupHost(host.c_str(), vIPs, nMaxIPs, true)) {
+ for (const CNetAddr& ip : vIPs) {
int nOneDay = 24*3600;
CAddress addr = CAddress(CService(ip, Params().GetDefaultPort()), requiredServiceBits);
- addr.nTime = GetTime() - 3*nOneDay - GetRand(4*nOneDay); // use a random age between 3 and 7 days old
+ addr.nTime = GetTime() - 3*nOneDay - rng.randrange(4*nOneDay); // use a random age between 3 and 7 days old
vAdd.push_back(addr);
found++;
}
@@ -1583,8 +1600,8 @@ void CConnman::ThreadDNSAddressSeed()
AddOneShot(seed);
}
}
+ --seeds_right_now;
}
-
LogPrintf("%d addresses found from DNS seeds\n", found);
}
@@ -1655,7 +1672,7 @@ int CConnman::GetExtraOutboundCount()
}
}
}
- return std::max(nOutbound - nMaxOutbound, 0);
+ return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0);
}
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
@@ -1715,7 +1732,8 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
CAddress addrConnect;
// Only connect out to one peer per network group (/16 for IPv4).
- int nOutbound = 0;
+ int nOutboundFullRelay = 0;
+ int nOutboundBlockRelay = 0;
std::set<std::vector<unsigned char> > setConnected;
{
LOCK(cs_vNodes);
@@ -1727,7 +1745,11 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
// also have the added issue that they're attacker controlled and could be used
// to prevent us from connecting to particular hosts if we used them here.
setConnected.insert(pnode->addr.GetGroup());
- nOutbound++;
+ if (pnode->m_tx_relay == nullptr) {
+ nOutboundBlockRelay++;
+ } else if (!pnode->fFeeler) {
+ nOutboundFullRelay++;
+ }
}
}
}
@@ -1746,7 +1768,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
//
bool fFeeler = false;
- if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) {
+ if (nOutboundFullRelay >= m_max_outbound_full_relay && nOutboundBlockRelay >= m_max_outbound_block_relay && !GetTryNewOutboundPeer()) {
int64_t nTime = GetTimeMicros(); // The current time right now (in microseconds).
if (nTime > nNextFeeler) {
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
@@ -1820,7 +1842,14 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
LogPrint(BCLog::NET, "Making feeler connection to %s\n", addrConnect.ToString());
}
- OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler);
+ // Open this connection as block-relay-only if we're already at our
+ // full-relay capacity, but not yet at our block-relay peer limit.
+ // (It should not be possible for fFeeler to be set if we're not
+ // also at our block-relay peer limit, but check against that as
+ // well for sanity.)
+ bool block_relay_only = nOutboundBlockRelay < m_max_outbound_block_relay && !fFeeler && nOutboundFullRelay >= m_max_outbound_full_relay;
+
+ OpenNetworkConnection(addrConnect, (int)setConnected.size() >= std::min(nMaxConnections - 1, 2), &grant, nullptr, false, fFeeler, false, block_relay_only);
}
}
}
@@ -1907,7 +1936,7 @@ void CConnman::ThreadOpenAddedConnections()
}
// if successful, this moves the passed grant to the constructed node
-void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection)
+void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound, const char *pszDest, bool fOneShot, bool fFeeler, bool manual_connection, bool block_relay_only)
{
//
// Initiate outbound network connection
@@ -1926,7 +1955,7 @@ void CConnman::OpenNetworkConnection(const CAddress& addrConnect, bool fCountFai
} else if (FindNode(std::string(pszDest)))
return;
- CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection);
+ CNode* pnode = ConnectNode(addrConnect, pszDest, fCountFailure, manual_connection, block_relay_only);
if (!pnode)
return;
@@ -2229,7 +2258,7 @@ bool CConnman::Start(CScheduler& scheduler, const Options& connOptions)
if (semOutbound == nullptr) {
// initialize semaphore
- semOutbound = MakeUnique<CSemaphore>(std::min((nMaxOutbound + nMaxFeeler), nMaxConnections));
+ semOutbound = MakeUnique<CSemaphore>(std::min(m_max_outbound, nMaxConnections));
}
if (semAddnode == nullptr) {
// initialize semaphore
@@ -2307,7 +2336,7 @@ void CConnman::Interrupt()
InterruptSocks5(true);
if (semOutbound) {
- for (int i=0; i<(nMaxOutbound + nMaxFeeler); i++) {
+ for (int i=0; i<m_max_outbound; i++) {
semOutbound->post();
}
}
@@ -2617,14 +2646,17 @@ int CConnman::GetBestHeight() const
unsigned int CConnman::GetReceiveFloodSize() const { return nReceiveFloodSize; }
-CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn)
+CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress& addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress& addrBindIn, const std::string& addrNameIn, bool fInboundIn, bool block_relay_only)
: nTimeConnected(GetSystemTimeInSeconds()),
addr(addrIn),
addrBind(addrBindIn),
fInbound(fInboundIn),
nKeyedNetGroup(nKeyedNetGroupIn),
addrKnown(5000, 0.001),
- filterInventoryKnown(50000, 0.000001),
+ // Don't relay addr messages to peers that we connect to as block-relay-only
+ // peers (to prevent adversaries from inferring these links from addr
+ // traffic).
+ m_addr_relay_peer(!block_relay_only),
id(idIn),
nLocalHostNonce(nLocalHostNonceIn),
nLocalServices(nLocalServicesIn),
@@ -2633,8 +2665,9 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn
hSocket = hSocketIn;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
hashContinue = uint256();
- filterInventoryKnown.reset();
- pfilter = MakeUnique<CBloomFilter>();
+ if (!block_relay_only) {
+ m_tx_relay = MakeUnique<TxRelay>();
+ }
for (const std::string &msg : getAllNetMessageTypes())
mapRecvBytesPerMsgCmd[msg] = 0;
diff --git a/src/net.h b/src/net.h
index 6c77d8135f..f33147155a 100644
--- a/src/net.h
+++ b/src/net.h
@@ -61,10 +61,12 @@ static const unsigned int MAX_ADDR_TO_SEND = 1000;
static const unsigned int MAX_PROTOCOL_MESSAGE_LENGTH = 4 * 1000 * 1000;
/** Maximum length of the user agent string in `version` message */
static const unsigned int MAX_SUBVERSION_LENGTH = 256;
-/** Maximum number of automatic outgoing nodes */
-static const int MAX_OUTBOUND_CONNECTIONS = 8;
+/** Maximum number of automatic outgoing nodes over which we'll relay everything (blocks, tx, addrs, etc) */
+static const int MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8;
/** Maximum number of addnode outgoing nodes */
static const int MAX_ADDNODE_CONNECTIONS = 8;
+/** Maximum number of block-relay-only outgoing connections */
+static const int MAX_BLOCKS_ONLY_CONNECTIONS = 2;
/** -listen default */
static const bool DEFAULT_LISTEN = true;
/** -upnp default */
@@ -131,7 +133,8 @@ public:
{
ServiceFlags nLocalServices = NODE_NONE;
int nMaxConnections = 0;
- int nMaxOutbound = 0;
+ int m_max_outbound_full_relay = 0;
+ int m_max_outbound_block_relay = 0;
int nMaxAddnode = 0;
int nMaxFeeler = 0;
int nBestHeight = 0;
@@ -155,10 +158,12 @@ public:
void Init(const Options& connOptions) {
nLocalServices = connOptions.nLocalServices;
nMaxConnections = connOptions.nMaxConnections;
- nMaxOutbound = std::min(connOptions.nMaxOutbound, connOptions.nMaxConnections);
+ m_max_outbound_full_relay = std::min(connOptions.m_max_outbound_full_relay, connOptions.nMaxConnections);
+ m_max_outbound_block_relay = connOptions.m_max_outbound_block_relay;
m_use_addrman_outgoing = connOptions.m_use_addrman_outgoing;
nMaxAddnode = connOptions.nMaxAddnode;
nMaxFeeler = connOptions.nMaxFeeler;
+ m_max_outbound = m_max_outbound_full_relay + m_max_outbound_block_relay + nMaxFeeler;
nBestHeight = connOptions.nBestHeight;
clientInterface = connOptions.uiInterface;
m_banman = connOptions.m_banman;
@@ -197,7 +202,7 @@ public:
bool GetNetworkActive() const { return fNetworkActive; };
bool GetUseAddrmanOutgoing() const { return m_use_addrman_outgoing; };
void SetNetworkActive(bool active);
- void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false);
+ void OpenNetworkConnection(const CAddress& addrConnect, bool fCountFailure, CSemaphoreGrant *grantOutbound = nullptr, const char *strDest = nullptr, bool fOneShot = false, bool fFeeler = false, bool manual_connection = false, bool block_relay_only = false);
bool CheckIncomingNonce(uint64_t nonce);
bool ForNode(NodeId id, std::function<bool(CNode* pnode)> func);
@@ -253,7 +258,7 @@ public:
void AddNewAddresses(const std::vector<CAddress>& vAddr, const CAddress& addrFrom, int64_t nTimePenalty = 0);
std::vector<CAddress> GetAddresses();
- // This allows temporarily exceeding nMaxOutbound, with the goal of finding
+ // This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding
// a peer that is better than all our current peers.
void SetTryNewOutboundPeer(bool flag);
bool GetTryNewOutboundPeer();
@@ -277,6 +282,12 @@ public:
bool DisconnectNode(const CNetAddr& addr);
bool DisconnectNode(NodeId id);
+ //! Used to convey which local services we are offering peers during node
+ //! connection.
+ //!
+ //! The data returned by this is used in CNode construction,
+ //! which is used to advertise which services we are offering
+ //! that peer during `net_processing.cpp:PushNodeVersion()`.
ServiceFlags GetLocalServices() const;
//!set the max outbound target in bytes
@@ -355,7 +366,7 @@ private:
CNode* FindNode(const CService& addr);
bool AttemptToEvictConnection();
- CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection);
+ CNode* ConnectNode(CAddress addrConnect, const char *pszDest, bool fCountFailure, bool manual_connection, bool block_relay_only);
void AddWhitelistPermissionFlags(NetPermissionFlags& flags, const CNetAddr &addr) const;
void DeleteNode(CNode* pnode);
@@ -408,15 +419,34 @@ private:
std::atomic<NodeId> nLastNodeId{0};
unsigned int nPrevNodeCount{0};
- /** Services this instance offers */
+ /**
+ * Services this instance offers.
+ *
+ * This data is replicated in each CNode instance we create during peer
+ * connection (in ConnectNode()) under a member also called
+ * nLocalServices.
+ *
+ * This data is not marked const, but after being set it should not
+ * change. See the note in CNode::nLocalServices documentation.
+ *
+ * \sa CNode::nLocalServices
+ */
ServiceFlags nLocalServices;
std::unique_ptr<CSemaphore> semOutbound;
std::unique_ptr<CSemaphore> semAddnode;
int nMaxConnections;
- int nMaxOutbound;
+
+ // How many full-relay (tx, block, addr) outbound peers we want
+ int m_max_outbound_full_relay;
+
+ // How many block-relay only outbound peers we want
+ // We do not relay tx or addr messages with these peers
+ int m_max_outbound_block_relay;
+
int nMaxAddnode;
int nMaxFeeler;
+ int m_max_outbound;
bool m_use_addrman_outgoing;
std::atomic<int> nBestHeight;
CClientUIInterface* clientInterface;
@@ -442,7 +472,7 @@ private:
std::thread threadMessageHandler;
/** flag for deciding to connect to an extra outbound peer,
- * in excess of nMaxOutbound
+ * in excess of m_max_outbound_full_relay
* This takes the place of a feeler connection */
std::atomic_bool m_try_another_outbound_peer;
@@ -681,15 +711,8 @@ public:
// Setting fDisconnect to true will cause the node to be disconnected the
// next time DisconnectNodes() runs
std::atomic_bool fDisconnect{false};
- // We use fRelayTxes for two purposes -
- // a) it allows us to not relay tx invs before receiving the peer's version message
- // b) the peer may tell us in its version message that we should not relay tx invs
- // unless it loads a bloom filter.
- bool fRelayTxes GUARDED_BY(cs_filter){false};
bool fSentAddr{false};
CSemaphoreGrant grantOutbound;
- mutable CCriticalSection cs_filter;
- std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter);
std::atomic<int> nRefCount{0};
const uint64_t nKeyedNetGroup;
@@ -708,28 +731,51 @@ public:
std::vector<CAddress> vAddrToSend;
CRollingBloomFilter addrKnown;
bool fGetAddr{false};
- std::set<uint256> setKnown;
int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0};
int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0};
- // inventory based relay
- CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_inventory);
- // Set of transaction ids we still have to announce.
- // They are sorted by the mempool before relay, so the order is not important.
- std::set<uint256> setInventoryTxToSend;
+ const bool m_addr_relay_peer;
+ bool IsAddrRelayPeer() const { return m_addr_relay_peer; }
+
// List of block ids we still have announce.
// There is no final sorting before sending, as they are always sent immediately
// and in the order requested.
std::vector<uint256> vInventoryBlockToSend GUARDED_BY(cs_inventory);
CCriticalSection cs_inventory;
- int64_t nNextInvSend{0};
+
+ struct TxRelay {
+ TxRelay() { pfilter = MakeUnique<CBloomFilter>(); }
+ mutable CCriticalSection cs_filter;
+ // We use fRelayTxes for two purposes -
+ // a) it allows us to not relay tx invs before receiving the peer's version message
+ // b) the peer may tell us in its version message that we should not relay tx invs
+ // unless it loads a bloom filter.
+ bool fRelayTxes GUARDED_BY(cs_filter){false};
+ std::unique_ptr<CBloomFilter> pfilter PT_GUARDED_BY(cs_filter) GUARDED_BY(cs_filter);
+
+ mutable CCriticalSection cs_tx_inventory;
+ CRollingBloomFilter filterInventoryKnown GUARDED_BY(cs_tx_inventory){50000, 0.000001};
+ // Set of transaction ids we still have to announce.
+ // They are sorted by the mempool before relay, so the order is not important.
+ std::set<uint256> setInventoryTxToSend;
+ // Used for BIP35 mempool sending
+ bool fSendMempool GUARDED_BY(cs_tx_inventory){false};
+ // Last time a "MEMPOOL" request was serviced.
+ std::atomic<int64_t> timeLastMempoolReq{0};
+ int64_t nNextInvSend{0};
+
+ CCriticalSection cs_feeFilter;
+ // Minimum fee rate with which to filter inv's to this node
+ CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0};
+ CAmount lastSentFeeFilter{0};
+ int64_t nextSendTimeFeeFilter{0};
+ };
+
+ // m_tx_relay == nullptr if we're not relaying transactions with this peer
+ std::unique_ptr<TxRelay> m_tx_relay;
+
// Used for headers announcements - unfiltered blocks to relay
std::vector<uint256> vBlockHashesToAnnounce GUARDED_BY(cs_inventory);
- // Used for BIP35 mempool sending
- bool fSendMempool GUARDED_BY(cs_inventory){false};
-
- // Last time a "MEMPOOL" request was serviced.
- std::atomic<int64_t> timeLastMempoolReq{0};
// Block and TXN accept times
std::atomic<int64_t> nLastBlockTime{0};
@@ -746,15 +792,10 @@ public:
std::atomic<int64_t> nMinPingUsecTime{std::numeric_limits<int64_t>::max()};
// Whether a ping is requested.
std::atomic<bool> fPingQueued{false};
- // Minimum fee rate with which to filter inv's to this node
- CAmount minFeeFilter GUARDED_BY(cs_feeFilter){0};
- CCriticalSection cs_feeFilter;
- CAmount lastSentFeeFilter{0};
- int64_t nextSendTimeFeeFilter{0};
std::set<uint256> orphan_work_set;
- CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false);
+ CNode(NodeId id, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn, uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn, const CAddress &addrBindIn, const std::string &addrNameIn = "", bool fInboundIn = false, bool block_relay_only = false);
~CNode();
CNode(const CNode&) = delete;
CNode& operator=(const CNode&) = delete;
@@ -762,8 +803,24 @@ public:
private:
const NodeId id;
const uint64_t nLocalHostNonce;
- // Services offered to this peer
+
+ //! Services offered to this peer.
+ //!
+ //! This is supplied by the parent CConnman during peer connection
+ //! (CConnman::ConnectNode()) from its attribute of the same name.
+ //!
+ //! This is const because there is no protocol defined for renegotiating
+ //! services initially offered to a peer. The set of local services we
+ //! offer should not change after initialization.
+ //!
+ //! An interesting example of this is NODE_NETWORK and initial block
+ //! download: a node which starts up from scratch doesn't have any blocks
+ //! to serve, but still advertises NODE_NETWORK because it will eventually
+ //! fulfill this role after IBD completes. P2P code is written in such a
+ //! way that it can gracefully handle peers who don't make good on their
+ //! service advertisements.
const ServiceFlags nLocalServices;
+
const int nMyStartingHeight;
int nSendVersion{0};
NetPermissionFlags m_permissionFlags{ PF_NONE };
@@ -847,20 +904,21 @@ public:
void AddInventoryKnown(const CInv& inv)
{
- {
- LOCK(cs_inventory);
- filterInventoryKnown.insert(inv.hash);
+ if (m_tx_relay != nullptr) {
+ LOCK(m_tx_relay->cs_tx_inventory);
+ m_tx_relay->filterInventoryKnown.insert(inv.hash);
}
}
void PushInventory(const CInv& inv)
{
- LOCK(cs_inventory);
- if (inv.type == MSG_TX) {
- if (!filterInventoryKnown.contains(inv.hash)) {
- setInventoryTxToSend.insert(inv.hash);
+ if (inv.type == MSG_TX && m_tx_relay != nullptr) {
+ LOCK(m_tx_relay->cs_tx_inventory);
+ if (!m_tx_relay->filterInventoryKnown.contains(inv.hash)) {
+ m_tx_relay->setInventoryTxToSend.insert(inv.hash);
}
} else if (inv.type == MSG_BLOCK) {
+ LOCK(cs_inventory);
vInventoryBlockToSend.push_back(inv.hash);
}
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 520dfcbb66..34d349e8e9 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -262,7 +262,7 @@ struct CNodeState {
bool fSupportsDesiredCmpctVersion;
/** State used to enforce CHAIN_SYNC_TIMEOUT
- * Only in effect for outbound, non-manual connections, with
+ * Only in effect for outbound, non-manual, full-relay connections, with
* m_protect == false
* Algorithm: if a peer's best known block has less work than our tip,
* set a timeout CHAIN_SYNC_TIMEOUT seconds in the future:
@@ -415,6 +415,9 @@ static void UpdatePreferredDownload(CNode* node, CNodeState* state) EXCLUSIVE_LO
static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime)
{
+ // Note that pnode->GetLocalServices() is a reflection of the local
+ // services we were offering when the CNode object was created for this
+ // peer.
ServiceFlags nLocalNodeServices = pnode->GetLocalServices();
uint64_t nonce = pnode->GetLocalNonce();
int nNodeStartingHeight = pnode->GetMyStartingHeight();
@@ -425,7 +428,7 @@ static void PushNodeVersion(CNode *pnode, CConnman* connman, int64_t nTime)
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
connman->PushMessage(pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
- nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes));
+ nonce, strSubVersion, nNodeStartingHeight, ::g_relay_txes && pnode->m_tx_relay != nullptr));
if (fLogIPs) {
LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), nodeid);
@@ -757,7 +760,7 @@ void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds)
}
// Returns true for outbound peers, excluding manual connections, feelers, and
-// one-shots
+// one-shots.
static bool IsOutboundDisconnectionCandidate(const CNode *node)
{
return !(node->fInbound || node->m_manual_connection || node->fFeeler || node->fOneShot);
@@ -1330,7 +1333,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma
assert(nRelayNodes <= best.size());
auto sortfunc = [&best, &hasher, nRelayNodes](CNode* pnode) {
- if (pnode->nVersion >= CADDR_TIME_VERSION) {
+ if (pnode->nVersion >= CADDR_TIME_VERSION && pnode->IsAddrRelayPeer()) {
uint64_t hashKey = CSipHasher(hasher).Write(pnode->GetId()).Finalize();
for (unsigned int i = 0; i < nRelayNodes; i++) {
if (hashKey > best[i].first) {
@@ -1449,11 +1452,11 @@ void static ProcessGetBlockData(CNode* pfrom, const CChainParams& chainparams, c
{
bool sendMerkleBlock = false;
CMerkleBlock merkleBlock;
- {
- LOCK(pfrom->cs_filter);
- if (pfrom->pfilter) {
+ if (pfrom->m_tx_relay != nullptr) {
+ LOCK(pfrom->m_tx_relay->cs_filter);
+ if (pfrom->m_tx_relay->pfilter) {
sendMerkleBlock = true;
- merkleBlock = CMerkleBlock(*pblock, *pfrom->pfilter);
+ merkleBlock = CMerkleBlock(*pblock, *pfrom->m_tx_relay->pfilter);
}
}
if (sendMerkleBlock) {
@@ -1513,7 +1516,12 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
std::vector<CInv> vNotFound;
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
- {
+
+ // Note that if we receive a getdata for a MSG_TX or MSG_WITNESS_TX from a
+ // block-relay-only outbound peer, we will stop processing further getdata
+ // messages from this peer (likely resulting in our peer eventually
+ // disconnecting us).
+ if (pfrom->m_tx_relay != nullptr) {
LOCK(cs_main);
while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
@@ -1533,11 +1541,11 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
if (mi != mapRelay.end()) {
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
push = true;
- } else if (pfrom->timeLastMempoolReq) {
+ } else if (pfrom->m_tx_relay->timeLastMempoolReq) {
auto txinfo = mempool.info(inv.hash);
// To protect privacy, do not answer getdata using the mempool when
// that TX couldn't have been INVed in reply to a MEMPOOL request.
- if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) {
+ if (txinfo.tx && txinfo.nTime <= pfrom->m_tx_relay->timeLastMempoolReq) {
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx));
push = true;
}
@@ -1773,9 +1781,11 @@ bool static ProcessHeadersMessage(CNode *pfrom, CConnman *connman, const std::ve
}
}
- if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr) {
- // If this is an outbound peer, check to see if we should protect
+ if (!pfrom->fDisconnect && IsOutboundDisconnectionCandidate(pfrom) && nodestate->pindexBestKnownBlock != nullptr && pfrom->m_tx_relay != nullptr) {
+ // If this is an outbound full-relay peer, check to see if we should protect
// it from the bad/lagging chain logic.
+ // Note that block-relay-only peers are already implicitly protected, so we
+ // only consider setting m_protect for the full-relay peers.
if (g_outbound_peers_with_protect_from_disconnect < MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT && nodestate->pindexBestKnownBlock->nChainWork >= ::ChainActive().Tip()->nChainWork && !nodestate->m_chain_sync.m_protect) {
LogPrint(BCLog::NET, "Protecting outbound peer=%d from eviction\n", pfrom->GetId());
nodestate->m_chain_sync.m_protect = true;
@@ -1996,9 +2006,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// set nodes not capable of serving the complete blockchain history as "limited nodes"
pfrom->m_limited_node = (!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED));
- {
- LOCK(pfrom->cs_filter);
- pfrom->fRelayTxes = fRelay; // set to true after we get the first filter* message
+ if (pfrom->m_tx_relay != nullptr) {
+ LOCK(pfrom->m_tx_relay->cs_filter);
+ pfrom->m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message
}
// Change version
@@ -2017,7 +2027,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
UpdatePreferredDownload(pfrom, State(pfrom->GetId()));
}
- if (!pfrom->fInbound)
+ if (!pfrom->fInbound && pfrom->IsAddrRelayPeer())
{
// Advertise our address
if (fListen && !::ChainstateActive().IsInitialBlockDownload())
@@ -2089,9 +2099,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// Mark this node as currently connected, so we update its timestamp later.
LOCK(cs_main);
State(pfrom->GetId())->fCurrentlyConnected = true;
- LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s\n",
- pfrom->nVersion.load(), pfrom->nStartingHeight, pfrom->GetId(),
- (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""));
+ LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n",
+ pfrom->nVersion.load(), pfrom->nStartingHeight,
+ pfrom->GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom->addr.ToString()) : ""),
+ pfrom->m_tx_relay == nullptr ? "block-relay" : "full-relay");
}
if (pfrom->nVersion >= SENDHEADERS_VERSION) {
@@ -2132,6 +2143,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
// Don't want addr from older versions unless seeding
if (pfrom->nVersion < CADDR_TIME_VERSION && connman->GetAddressCount() > 1000)
return true;
+ if (!pfrom->IsAddrRelayPeer()) {
+ return true;
+ }
if (vAddr.size() > 1000)
{
LOCK(cs_main);
@@ -2215,7 +2229,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
return false;
}
- bool fBlocksOnly = !g_relay_txes;
+ // We won't accept tx inv's if we're in blocks-only mode, or this is a
+ // block-relay-only peer
+ bool fBlocksOnly = !g_relay_txes || (pfrom->m_tx_relay == nullptr);
// Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
if (pfrom->HasPermission(PF_RELAY))
@@ -2254,7 +2270,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
{
pfrom->AddInventoryKnown(inv);
if (fBlocksOnly) {
- LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId());
+ LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol, disconnecting peer=%d\n", inv.hash.ToString(), pfrom->GetId());
+ pfrom->fDisconnect = true;
+ return true;
} else if (!fAlreadyHave && !fImporting && !fReindex && !::ChainstateActive().IsInitialBlockDownload()) {
RequestTx(State(pfrom->GetId()), inv.hash, current_time);
}
@@ -2471,9 +2489,11 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
if (strCommand == NetMsgType::TX) {
// Stop processing the transaction early if
// We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off
- if (!g_relay_txes && !pfrom->HasPermission(PF_RELAY))
+ // or if this peer is supposed to be a block-relay-only peer
+ if ((!g_relay_txes && !pfrom->HasPermission(PF_RELAY)) || (pfrom->m_tx_relay == nullptr))
{
LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId());
+ pfrom->fDisconnect = true;
return true;
}
@@ -2539,7 +2559,7 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
}
AddOrphanTx(ptx, pfrom->GetId());
- // DoS prevention: do not allow mapOrphanTransactions to grow unbounded
+ // DoS prevention: do not allow mapOrphanTransactions to grow unbounded (see CVE-2012-3789)
unsigned int nMaxOrphanTx = (unsigned int)std::max((int64_t)0, gArgs.GetArg("-maxorphantx", DEFAULT_MAX_ORPHAN_TRANSACTIONS));
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0) {
@@ -2990,6 +3010,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
LogPrint(BCLog::NET, "Ignoring \"getaddr\" from outbound connection. peer=%d\n", pfrom->GetId());
return true;
}
+ if (!pfrom->IsAddrRelayPeer()) {
+ LogPrint(BCLog::NET, "Ignoring \"getaddr\" from block-relay-only connection. peer=%d\n", pfrom->GetId());
+ return true;
+ }
// Only send one GetAddr response per connection to reduce resource waste
// and discourage addr stamping of INV announcements.
@@ -3031,8 +3055,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
return true;
}
- LOCK(pfrom->cs_inventory);
- pfrom->fSendMempool = true;
+ if (pfrom->m_tx_relay != nullptr) {
+ LOCK(pfrom->m_tx_relay->cs_tx_inventory);
+ pfrom->m_tx_relay->fSendMempool = true;
+ }
return true;
}
@@ -3123,12 +3149,12 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 100);
}
- else
+ else if (pfrom->m_tx_relay != nullptr)
{
- LOCK(pfrom->cs_filter);
- pfrom->pfilter.reset(new CBloomFilter(filter));
- pfrom->pfilter->UpdateEmptyFull();
- pfrom->fRelayTxes = true;
+ LOCK(pfrom->m_tx_relay->cs_filter);
+ pfrom->m_tx_relay->pfilter.reset(new CBloomFilter(filter));
+ pfrom->m_tx_relay->pfilter->UpdateEmptyFull();
+ pfrom->m_tx_relay->fRelayTxes = true;
}
return true;
}
@@ -3142,10 +3168,10 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
bool bad = false;
if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
bad = true;
- } else {
- LOCK(pfrom->cs_filter);
- if (pfrom->pfilter) {
- pfrom->pfilter->insert(vData);
+ } else if (pfrom->m_tx_relay != nullptr) {
+ LOCK(pfrom->m_tx_relay->cs_filter);
+ if (pfrom->m_tx_relay->pfilter) {
+ pfrom->m_tx_relay->pfilter->insert(vData);
} else {
bad = true;
}
@@ -3158,11 +3184,14 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
}
if (strCommand == NetMsgType::FILTERCLEAR) {
- LOCK(pfrom->cs_filter);
+ if (pfrom->m_tx_relay == nullptr) {
+ return true;
+ }
+ LOCK(pfrom->m_tx_relay->cs_filter);
if (pfrom->GetLocalServices() & NODE_BLOOM) {
- pfrom->pfilter.reset(new CBloomFilter());
+ pfrom->m_tx_relay->pfilter.reset(new CBloomFilter());
}
- pfrom->fRelayTxes = true;
+ pfrom->m_tx_relay->fRelayTxes = true;
return true;
}
@@ -3170,9 +3199,9 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr
CAmount newFeeFilter = 0;
vRecv >> newFeeFilter;
if (MoneyRange(newFeeFilter)) {
- {
- LOCK(pfrom->cs_feeFilter);
- pfrom->minFeeFilter = newFeeFilter;
+ if (pfrom->m_tx_relay != nullptr) {
+ LOCK(pfrom->m_tx_relay->cs_feeFilter);
+ pfrom->m_tx_relay->minFeeFilter = newFeeFilter;
}
LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n", CFeeRate(newFeeFilter).ToString(), pfrom->GetId());
}
@@ -3449,6 +3478,8 @@ void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds)
if (state == nullptr) return; // shouldn't be possible, but just in case
// Don't evict our protected peers
if (state->m_chain_sync.m_protect) return;
+ // Don't evict our block-relay-only peers.
+ if (pnode->m_tx_relay == nullptr) return;
if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) {
worst_peer = pnode->GetId();
oldest_block_announcement = state->m_last_block_announcement;
@@ -3576,7 +3607,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// Address refresh broadcast
int64_t nNow = GetTimeMicros();
- if (!::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) {
+ if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) {
AdvertiseLocal(pto);
pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
@@ -3584,7 +3615,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
//
// Message: addr
//
- if (pto->nNextAddrSend < nNow) {
+ if (pto->IsAddrRelayPeer() && pto->nNextAddrSend < nNow) {
pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size());
@@ -3792,120 +3823,123 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
}
pto->vInventoryBlockToSend.clear();
- // Check whether periodic sends should happen
- bool fSendTrickle = pto->HasPermission(PF_NOBAN);
- if (pto->nNextInvSend < nNow) {
- fSendTrickle = true;
- if (pto->fInbound) {
- pto->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL);
- } else {
- // Use half the delay for outbound peers, as there is less privacy concern for them.
- pto->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1);
+ if (pto->m_tx_relay != nullptr) {
+ LOCK(pto->m_tx_relay->cs_tx_inventory);
+ // Check whether periodic sends should happen
+ bool fSendTrickle = pto->HasPermission(PF_NOBAN);
+ if (pto->m_tx_relay->nNextInvSend < nNow) {
+ fSendTrickle = true;
+ if (pto->fInbound) {
+ pto->m_tx_relay->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL);
+ } else {
+ // Use half the delay for outbound peers, as there is less privacy concern for them.
+ pto->m_tx_relay->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1);
+ }
}
- }
-
- // Time to send but the peer has requested we not relay transactions.
- if (fSendTrickle) {
- LOCK(pto->cs_filter);
- if (!pto->fRelayTxes) pto->setInventoryTxToSend.clear();
- }
- // Respond to BIP35 mempool requests
- if (fSendTrickle && pto->fSendMempool) {
- auto vtxinfo = mempool.infoAll();
- pto->fSendMempool = false;
- CAmount filterrate = 0;
- {
- LOCK(pto->cs_feeFilter);
- filterrate = pto->minFeeFilter;
+ // Time to send but the peer has requested we not relay transactions.
+ if (fSendTrickle) {
+ LOCK(pto->m_tx_relay->cs_filter);
+ if (!pto->m_tx_relay->fRelayTxes) pto->m_tx_relay->setInventoryTxToSend.clear();
}
- LOCK(pto->cs_filter);
-
- for (const auto& txinfo : vtxinfo) {
- const uint256& hash = txinfo.tx->GetHash();
- CInv inv(MSG_TX, hash);
- pto->setInventoryTxToSend.erase(hash);
- if (filterrate) {
- if (txinfo.feeRate.GetFeePerK() < filterrate)
- continue;
- }
- if (pto->pfilter) {
- if (!pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
+ // Respond to BIP35 mempool requests
+ if (fSendTrickle && pto->m_tx_relay->fSendMempool) {
+ auto vtxinfo = mempool.infoAll();
+ pto->m_tx_relay->fSendMempool = false;
+ CAmount filterrate = 0;
+ {
+ LOCK(pto->m_tx_relay->cs_feeFilter);
+ filterrate = pto->m_tx_relay->minFeeFilter;
}
- pto->filterInventoryKnown.insert(hash);
- vInv.push_back(inv);
- if (vInv.size() == MAX_INV_SZ) {
- connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
- vInv.clear();
+
+ LOCK(pto->m_tx_relay->cs_filter);
+
+ for (const auto& txinfo : vtxinfo) {
+ const uint256& hash = txinfo.tx->GetHash();
+ CInv inv(MSG_TX, hash);
+ pto->m_tx_relay->setInventoryTxToSend.erase(hash);
+ if (filterrate) {
+ if (txinfo.feeRate.GetFeePerK() < filterrate)
+ continue;
+ }
+ if (pto->m_tx_relay->pfilter) {
+ if (!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
+ }
+ pto->m_tx_relay->filterInventoryKnown.insert(hash);
+ vInv.push_back(inv);
+ if (vInv.size() == MAX_INV_SZ) {
+ connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
+ vInv.clear();
+ }
}
+ pto->m_tx_relay->timeLastMempoolReq = GetTime();
}
- pto->timeLastMempoolReq = GetTime();
- }
- // Determine transactions to relay
- if (fSendTrickle) {
- // Produce a vector with all candidates for sending
- std::vector<std::set<uint256>::iterator> vInvTx;
- vInvTx.reserve(pto->setInventoryTxToSend.size());
- for (std::set<uint256>::iterator it = pto->setInventoryTxToSend.begin(); it != pto->setInventoryTxToSend.end(); it++) {
- vInvTx.push_back(it);
- }
- CAmount filterrate = 0;
- {
- LOCK(pto->cs_feeFilter);
- filterrate = pto->minFeeFilter;
- }
- // Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
- // A heap is used so that not all items need sorting if only a few are being sent.
- CompareInvMempoolOrder compareInvMempoolOrder(&mempool);
- std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
- // No reason to drain out at many times the network's capacity,
- // especially since we have many peers and some will draw much shorter delays.
- unsigned int nRelayedTransactions = 0;
- LOCK(pto->cs_filter);
- while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) {
- // Fetch the top element from the heap
- std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
- std::set<uint256>::iterator it = vInvTx.back();
- vInvTx.pop_back();
- uint256 hash = *it;
- // Remove it from the to-be-sent set
- pto->setInventoryTxToSend.erase(it);
- // Check if not in the filter already
- if (pto->filterInventoryKnown.contains(hash)) {
- continue;
+ // Determine transactions to relay
+ if (fSendTrickle) {
+ // Produce a vector with all candidates for sending
+ std::vector<std::set<uint256>::iterator> vInvTx;
+ vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size());
+ for (std::set<uint256>::iterator it = pto->m_tx_relay->setInventoryTxToSend.begin(); it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) {
+ vInvTx.push_back(it);
}
- // Not in the mempool anymore? don't bother sending it.
- auto txinfo = mempool.info(hash);
- if (!txinfo.tx) {
- continue;
- }
- if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) {
- continue;
- }
- if (pto->pfilter && !pto->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
- // Send
- vInv.push_back(CInv(MSG_TX, hash));
- nRelayedTransactions++;
+ CAmount filterrate = 0;
{
- // Expire old relay messages
- while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow)
- {
- mapRelay.erase(vRelayExpiration.front().second);
- vRelayExpiration.pop_front();
+ LOCK(pto->m_tx_relay->cs_feeFilter);
+ filterrate = pto->m_tx_relay->minFeeFilter;
+ }
+ // Topologically and fee-rate sort the inventory we send for privacy and priority reasons.
+ // A heap is used so that not all items need sorting if only a few are being sent.
+ CompareInvMempoolOrder compareInvMempoolOrder(&mempool);
+ std::make_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
+ // No reason to drain out at many times the network's capacity,
+ // especially since we have many peers and some will draw much shorter delays.
+ unsigned int nRelayedTransactions = 0;
+ LOCK(pto->m_tx_relay->cs_filter);
+ while (!vInvTx.empty() && nRelayedTransactions < INVENTORY_BROADCAST_MAX) {
+ // Fetch the top element from the heap
+ std::pop_heap(vInvTx.begin(), vInvTx.end(), compareInvMempoolOrder);
+ std::set<uint256>::iterator it = vInvTx.back();
+ vInvTx.pop_back();
+ uint256 hash = *it;
+ // Remove it from the to-be-sent set
+ pto->m_tx_relay->setInventoryTxToSend.erase(it);
+ // Check if not in the filter already
+ if (pto->m_tx_relay->filterInventoryKnown.contains(hash)) {
+ continue;
}
+ // Not in the mempool anymore? don't bother sending it.
+ auto txinfo = mempool.info(hash);
+ if (!txinfo.tx) {
+ continue;
+ }
+ if (filterrate && txinfo.feeRate.GetFeePerK() < filterrate) {
+ continue;
+ }
+ if (pto->m_tx_relay->pfilter && !pto->m_tx_relay->pfilter->IsRelevantAndUpdate(*txinfo.tx)) continue;
+ // Send
+ vInv.push_back(CInv(MSG_TX, hash));
+ nRelayedTransactions++;
+ {
+ // Expire old relay messages
+ while (!vRelayExpiration.empty() && vRelayExpiration.front().first < nNow)
+ {
+ mapRelay.erase(vRelayExpiration.front().second);
+ vRelayExpiration.pop_front();
+ }
- auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx)));
- if (ret.second) {
- vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first));
+ auto ret = mapRelay.insert(std::make_pair(hash, std::move(txinfo.tx)));
+ if (ret.second) {
+ vRelayExpiration.push_back(std::make_pair(nNow + 15 * 60 * 1000000, ret.first));
+ }
}
+ if (vInv.size() == MAX_INV_SZ) {
+ connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
+ vInv.clear();
+ }
+ pto->m_tx_relay->filterInventoryKnown.insert(hash);
}
- if (vInv.size() == MAX_INV_SZ) {
- connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
- vInv.clear();
- }
- pto->filterInventoryKnown.insert(hash);
}
}
}
@@ -4066,27 +4100,27 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
// Message: feefilter
//
// We don't want white listed peers to filter txs to us if we have -whitelistforcerelay
- if (pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
+ if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
!pto->HasPermission(PF_FORCERELAY)) {
CAmount currentFilter = mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
int64_t timeNow = GetTimeMicros();
- if (timeNow > pto->nextSendTimeFeeFilter) {
+ if (timeNow > pto->m_tx_relay->nextSendTimeFeeFilter) {
static CFeeRate default_feerate(DEFAULT_MIN_RELAY_TX_FEE);
static FeeFilterRounder filterRounder(default_feerate);
CAmount filterToSend = filterRounder.round(currentFilter);
// We always have a fee filter of at least minRelayTxFee
filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK());
- if (filterToSend != pto->lastSentFeeFilter) {
+ if (filterToSend != pto->m_tx_relay->lastSentFeeFilter) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend));
- pto->lastSentFeeFilter = filterToSend;
+ pto->m_tx_relay->lastSentFeeFilter = filterToSend;
}
- pto->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
+ pto->m_tx_relay->nextSendTimeFeeFilter = PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
}
// If the fee filter has changed substantially and it's still more than MAX_FEEFILTER_CHANGE_DELAY
// until scheduled broadcast, then move the broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
- else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->nextSendTimeFeeFilter &&
- (currentFilter < 3 * pto->lastSentFeeFilter / 4 || currentFilter > 4 * pto->lastSentFeeFilter / 3)) {
- pto->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
+ else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 < pto->m_tx_relay->nextSendTimeFeeFilter &&
+ (currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 || currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) {
+ pto->m_tx_relay->nextSendTimeFeeFilter = timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
}
}
}
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp
index a89a15bc9d..2ababb5e1e 100644
--- a/src/qt/askpassphrasedialog.cpp
+++ b/src/qt/askpassphrasedialog.cpp
@@ -18,12 +18,13 @@
#include <QMessageBox>
#include <QPushButton>
-AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) :
+AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) :
QDialog(parent),
ui(new Ui::AskPassphraseDialog),
mode(_mode),
model(nullptr),
- fCapsLock(false)
+ fCapsLock(false),
+ m_passphrase_out(passphrase_out)
{
ui->setupUi(this);
@@ -43,7 +44,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) :
switch(mode)
{
case Encrypt: // Ask passphrase x2
- ui->warningLabel->setText(tr("Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>."));
+ ui->warningLabel->setText(tr("Enter the new passphrase for the wallet.<br/>Please use a passphrase of <b>ten or more random characters</b>, or <b>eight or more words</b>."));
ui->passLabel1->hide();
ui->passEdit1->hide();
setWindowTitle(tr("Encrypt wallet"));
@@ -66,7 +67,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) :
break;
case ChangePass: // Ask old passphrase + new passphrase x2
setWindowTitle(tr("Change passphrase"));
- ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase to the wallet."));
+ ui->warningLabel->setText(tr("Enter the old passphrase and new passphrase for the wallet."));
break;
}
textChanged();
@@ -90,7 +91,7 @@ void AskPassphraseDialog::setModel(WalletModel *_model)
void AskPassphraseDialog::accept()
{
SecureString oldpass, newpass1, newpass2;
- if(!model)
+ if (!model && mode != Encrypt)
return;
oldpass.reserve(MAX_PASSPHRASE_SIZE);
newpass1.reserve(MAX_PASSPHRASE_SIZE);
@@ -119,24 +120,33 @@ void AskPassphraseDialog::accept()
{
if(newpass1 == newpass2)
{
- if(model->setWalletEncrypted(true, newpass1))
- {
- QMessageBox::warning(this, tr("Wallet encrypted"),
+ QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully protect "
+ "your bitcoins from being stolen by malware infecting your computer.");
+ if (m_passphrase_out) {
+ m_passphrase_out->assign(newpass1);
+ QMessageBox::warning(this, tr("Wallet to be encrypted"),
"<qt>" +
- tr("Your wallet is now encrypted. "
- "Remember that encrypting your wallet cannot fully protect "
- "your bitcoins from being stolen by malware infecting your computer.") +
- "<br><br><b>" +
- tr("IMPORTANT: Any previous backups you have made of your wallet file "
- "should be replaced with the newly generated, encrypted wallet file. "
- "For security reasons, previous backups of the unencrypted wallet file "
- "will become useless as soon as you start using the new, encrypted wallet.") +
+ tr("Your wallet is about to be encrypted. ") + encryption_reminder +
"</b></qt>");
- }
- else
- {
- QMessageBox::critical(this, tr("Wallet encryption failed"),
- tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted."));
+ } else {
+ assert(model != nullptr);
+ if(model->setWalletEncrypted(true, newpass1))
+ {
+ QMessageBox::warning(this, tr("Wallet encrypted"),
+ "<qt>" +
+ tr("Your wallet is now encrypted. ") + encryption_reminder +
+ "<br><br><b>" +
+ tr("IMPORTANT: Any previous backups you have made of your wallet file "
+ "should be replaced with the newly generated, encrypted wallet file. "
+ "For security reasons, previous backups of the unencrypted wallet file "
+ "will become useless as soon as you start using the new, encrypted wallet.") +
+ "</b></qt>");
+ }
+ else
+ {
+ QMessageBox::critical(this, tr("Wallet encryption failed"),
+ tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted."));
+ }
}
QDialog::accept(); // Success
}
diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h
index ac31569f63..bdfd3fb9a0 100644
--- a/src/qt/askpassphrasedialog.h
+++ b/src/qt/askpassphrasedialog.h
@@ -7,6 +7,8 @@
#include <QDialog>
+#include <support/allocators/secure.h>
+
class WalletModel;
namespace Ui {
@@ -27,7 +29,7 @@ public:
Decrypt /**< Ask passphrase and decrypt wallet */
};
- explicit AskPassphraseDialog(Mode mode, QWidget *parent);
+ explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr);
~AskPassphraseDialog();
void accept();
@@ -39,6 +41,7 @@ private:
Mode mode;
WalletModel *model;
bool fCapsLock;
+ SecureString* m_passphrase_out;
private Q_SLOTS:
void textChanged();
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index adc19df935..46f8deee57 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -282,6 +282,10 @@ void BitcoinApplication::parameterSetup()
m_node.initParameterInteraction();
}
+void BitcoinApplication::SetPrune(bool prune, bool force) {
+ optionsModel->SetPrune(prune, force);
+}
+
void BitcoinApplication::requestInitialize()
{
qDebug() << __func__ << ": Requesting initialize";
@@ -487,8 +491,10 @@ int GuiMain(int argc, char* argv[])
/// 5. Now that settings and translations are available, ask user for data directory
// User language is set up: pick a data directory
- if (!Intro::pickDataDirectory(*node))
- return EXIT_SUCCESS;
+ bool did_show_intro = false;
+ bool prune = false; // Intro dialog prune check box
+ // Gracefully exit if the user cancels
+ if (!Intro::showIfNeeded(*node, did_show_intro, prune)) return EXIT_SUCCESS;
/// 6. Determine availability of data directory and parse bitcoin.conf
/// - Do not call GetDataDir(true) before this step finishes
@@ -511,7 +517,7 @@ int GuiMain(int argc, char* argv[])
// - QSettings() will use the new application name after this, resulting in network-specific settings
// - Needs to be done before createOptionsModel
- // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause)
+ // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause)
try {
node->selectParams(gArgs.GetChainName());
} catch(std::exception &e) {
@@ -524,7 +530,7 @@ int GuiMain(int argc, char* argv[])
PaymentServer::ipcParseCommandLine(*node, argc, argv);
#endif
- QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
+ QScopedPointer<const NetworkStyle> networkStyle(NetworkStyle::instantiate(Params().NetworkIDString()));
assert(!networkStyle.isNull());
// Allow for separate UI settings for testnets
QApplication::setApplicationName(networkStyle->getAppName());
@@ -562,6 +568,11 @@ int GuiMain(int argc, char* argv[])
// Load GUI settings from QSettings
app.createOptionsModel(gArgs.GetBoolArg("-resetguisettings", false));
+ if (did_show_intro) {
+ // Store intro dialog settings other than datadir (network specific)
+ app.SetPrune(prune, true);
+ }
+
if (gArgs.GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !gArgs.GetBoolArg("-min", false))
app.createSplashScreen(networkStyle.data());
diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h
index 3869193a3a..8c77fd8a7d 100644
--- a/src/qt/bitcoin.h
+++ b/src/qt/bitcoin.h
@@ -67,6 +67,8 @@ public:
void parameterSetup();
/// Create options model
void createOptionsModel(bool resetSettings);
+ /// Update prune value
+ void SetPrune(bool prune, bool force = false);
/// Create main window
void createWindow(const NetworkStyle *networkStyle);
/// Create splash screen
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 323797a4b6..7671fde705 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -6,6 +6,7 @@
#include <qt/bitcoinunits.h>
#include <qt/clientmodel.h>
+#include <qt/createwalletdialog.h>
#include <qt/guiconstants.h>
#include <qt/guiutil.h>
#include <qt/modaloverlay.h>
@@ -339,6 +340,9 @@ void BitcoinGUI::createActions()
m_close_wallet_action = new QAction(tr("Close Wallet..."), this);
m_close_wallet_action->setStatusTip(tr("Close wallet"));
+ m_create_wallet_action = new QAction(tr("Create Wallet..."), this);
+ m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
+
showHelpMessageAction = new QAction(tr("&Command-line options"), this);
showHelpMessageAction->setMenuRole(QAction::NoRole);
showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(PACKAGE_NAME));
@@ -371,6 +375,10 @@ void BitcoinGUI::createActions()
for (const std::pair<const std::string, bool>& i : m_wallet_controller->listWalletDir()) {
const std::string& path = i.first;
QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);
+ // Menu items remove single &. Single & are shown when && is in
+ // the string, but only the first occurrence. So replace only
+ // the first & with &&.
+ name.replace(name.indexOf(QChar('&')), 1, QString("&&"));
QAction* action = m_open_wallet_menu->addAction(name);
if (i.second) {
@@ -379,31 +387,11 @@ void BitcoinGUI::createActions()
continue;
}
- connect(action, &QAction::triggered, [this, name, path] {
- OpenWalletActivity* activity = m_wallet_controller->openWallet(path);
-
- QProgressDialog* dialog = new QProgressDialog(this);
- dialog->setLabelText(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped()));
- dialog->setRange(0, 0);
- dialog->setCancelButton(nullptr);
- dialog->setWindowModality(Qt::ApplicationModal);
- dialog->show();
-
- connect(activity, &OpenWalletActivity::message, this, [this] (QMessageBox::Icon icon, QString text) {
- QMessageBox box;
- box.setIcon(icon);
- box.setText(tr("Open Wallet Failed"));
- box.setInformativeText(text);
- box.setStandardButtons(QMessageBox::Ok);
- box.setDefaultButton(QMessageBox::Ok);
- connect(this, &QObject::destroyed, &box, &QDialog::accept);
- box.exec();
- });
+ connect(action, &QAction::triggered, [this, path] {
+ auto activity = new OpenWalletActivity(m_wallet_controller, this);
connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet);
connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater);
- connect(activity, &OpenWalletActivity::finished, dialog, &QObject::deleteLater);
- bool invoked = QMetaObject::invokeMethod(activity, "open");
- assert(invoked);
+ activity->open(path);
});
}
if (m_open_wallet_menu->isEmpty()) {
@@ -414,6 +402,12 @@ void BitcoinGUI::createActions()
connect(m_close_wallet_action, &QAction::triggered, [this] {
m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this);
});
+ connect(m_create_wallet_action, &QAction::triggered, [this] {
+ auto activity = new CreateWalletActivity(m_wallet_controller, this);
+ connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet);
+ connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
+ activity->create();
+ });
}
#endif // ENABLE_WALLET
@@ -435,6 +429,7 @@ void BitcoinGUI::createMenuBar()
QMenu *file = appMenuBar->addMenu(tr("&File"));
if(walletFrame)
{
+ file->addAction(m_create_wallet_action);
file->addAction(m_open_wallet_action);
file->addAction(m_close_wallet_action);
file->addSeparator();
@@ -480,24 +475,16 @@ void BitcoinGUI::createMenuBar()
connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) {
zoom_action->setEnabled(window != nullptr);
});
-#else
- QAction* restore_action = window_menu->addAction(tr("Restore"));
- connect(restore_action, &QAction::triggered, [] {
- qApp->focusWindow()->showNormal();
- });
-
- connect(qApp, &QApplication::focusWindowChanged, [restore_action] (QWindow* window) {
- restore_action->setEnabled(window != nullptr);
- });
#endif
if (walletFrame) {
+#ifdef Q_OS_MAC
window_menu->addSeparator();
QAction* main_window_action = window_menu->addAction(tr("Main Window"));
connect(main_window_action, &QAction::triggered, [this] {
GUIUtil::bringToFront(this);
});
-
+#endif
window_menu->addSeparator();
window_menu->addAction(usedSendingAddressesAction);
window_menu->addAction(usedReceivingAddressesAction);
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 46ced79007..809cf8b4ed 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -147,6 +147,7 @@ private:
QAction* openRPCConsoleAction = nullptr;
QAction* openAction = nullptr;
QAction* showHelpMessageAction = nullptr;
+ QAction* m_create_wallet_action{nullptr};
QAction* m_open_wallet_action{nullptr};
QMenu* m_open_wallet_menu{nullptr};
QAction* m_close_wallet_action{nullptr};
diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp
index 5cde21eec6..3d40ee7823 100644
--- a/src/qt/bitcoinstrings.cpp
+++ b/src/qt/bitcoinstrings.cpp
@@ -178,6 +178,8 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate initial keys"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to generate keys"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to start HTTP server. See debug log for details."),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown -blockfilterindex value %s."),
+QT_TRANSLATE_NOOP("bitcoin-core", "Unknown address type '%s'"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Unknown change type '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unknown network specified in -onlynet: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unsupported logging category %s=%s."),
QT_TRANSLATE_NOOP("bitcoin-core", "Upgrading UTXO database"),
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
new file mode 100644
index 0000000000..8e6474b0d4
--- /dev/null
+++ b/src/qt/createwalletdialog.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#if defined(HAVE_CONFIG_H)
+#include <config/bitcoin-config.h>
+#endif
+
+#include <qt/createwalletdialog.h>
+#include <qt/forms/ui_createwalletdialog.h>
+
+#include <QPushButton>
+
+CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::CreateWalletDialog)
+{
+ ui->setupUi(this);
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create"));
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+ ui->wallet_name_line_edit->setFocus(Qt::ActiveWindowFocusReason);
+
+ connect(ui->wallet_name_line_edit, &QLineEdit::textEdited, [this](const QString& text) {
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
+ });
+
+ connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) {
+ // Disable the disable_privkeys_checkbox when isEncryptWalletChecked is
+ // set to true, enable it when isEncryptWalletChecked is false.
+ ui->disable_privkeys_checkbox->setEnabled(!checked);
+
+ // When the disable_privkeys_checkbox is disabled, uncheck it.
+ if (!ui->disable_privkeys_checkbox->isEnabled()) {
+ ui->disable_privkeys_checkbox->setChecked(false);
+ }
+ });
+}
+
+CreateWalletDialog::~CreateWalletDialog()
+{
+ delete ui;
+}
+
+QString CreateWalletDialog::walletName() const
+{
+ return ui->wallet_name_line_edit->text();
+}
+
+bool CreateWalletDialog::isEncryptWalletChecked() const
+{
+ return ui->encrypt_wallet_checkbox->isChecked();
+}
+
+bool CreateWalletDialog::isDisablePrivateKeysChecked() const
+{
+ return ui->disable_privkeys_checkbox->isChecked();
+}
+
+bool CreateWalletDialog::isMakeBlankWalletChecked() const
+{
+ return ui->blank_wallet_checkbox->isChecked();
+}
diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h
new file mode 100644
index 0000000000..30766107b9
--- /dev/null
+++ b/src/qt/createwalletdialog.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_QT_CREATEWALLETDIALOG_H
+#define BITCOIN_QT_CREATEWALLETDIALOG_H
+
+#include <QDialog>
+
+class WalletModel;
+
+namespace Ui {
+ class CreateWalletDialog;
+}
+
+/** Dialog for creating wallets
+ */
+class CreateWalletDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit CreateWalletDialog(QWidget* parent);
+ virtual ~CreateWalletDialog();
+
+ QString walletName() const;
+ bool isEncryptWalletChecked() const;
+ bool isDisablePrivateKeysChecked() const;
+ bool isMakeBlankWalletChecked() const;
+
+private:
+ Ui::CreateWalletDialog *ui;
+};
+
+#endif // BITCOIN_QT_CREATEWALLETDIALOG_H
diff --git a/src/qt/forms/askpassphrasedialog.ui b/src/qt/forms/askpassphrasedialog.ui
index 69803989cd..e74d183818 100644
--- a/src/qt/forms/askpassphrasedialog.ui
+++ b/src/qt/forms/askpassphrasedialog.ui
@@ -95,7 +95,7 @@
<item row="3" column="1">
<widget class="QCheckBox" name="toggleShowPasswordButton">
<property name="text">
- <string>Show password</string>
+ <string>Show passphrase</string>
</property>
</widget>
</item>
diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui
new file mode 100644
index 0000000000..e49bab8f3b
--- /dev/null
+++ b/src/qt/forms/createwalletdialog.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CreateWalletDialog</class>
+ <widget class="QDialog" name="CreateWalletDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>364</width>
+ <height>185</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create Wallet</string>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>140</y>
+ <width>341</width>
+ <height>32</height>
+ </rect>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ <widget class="QLineEdit" name="wallet_name_line_edit">
+ <property name="geometry">
+ <rect>
+ <x>120</x>
+ <y>20</y>
+ <width>231</width>
+ <height>24</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="label">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>20</y>
+ <width>101</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Wallet Name</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" name="encrypt_wallet_checkbox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>50</y>
+ <width>171</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="toolTip">
+ <string>Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</string>
+ </property>
+ <property name="text">
+ <string>Encrypt Wallet</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox" name="disable_privkeys_checkbox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>80</y>
+ <width>171</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="toolTip">
+ <string>Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</string>
+ </property>
+ <property name="text">
+ <string>Disable Private Keys</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" name="blank_wallet_checkbox">
+ <property name="geometry">
+ <rect>
+ <x>20</x>
+ <y>110</y>
+ <width>171</width>
+ <height>22</height>
+ </rect>
+ </property>
+ <property name="toolTip">
+ <string>Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</string>
+ </property>
+ <property name="text">
+ <string>Make Blank Wallet</string>
+ </property>
+ </widget>
+ </widget>
+ <tabstops>
+ <tabstop>wallet_name_line_edit</tabstop>
+ <tabstop>encrypt_wallet_checkbox</tabstop>
+ <tabstop>disable_privkeys_checkbox</tabstop>
+ <tabstop>blank_wallet_checkbox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>CreateWalletDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>CreateWalletDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui
index cfdd8482e3..f27a4ebe44 100644
--- a/src/qt/forms/intro.ui
+++ b/src/qt/forms/intro.ui
@@ -211,6 +211,16 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="prune">
+ <property name="toolTip">
+ <string>Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</string>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QLabel" name="lblExplanation2">
<property name="text">
<string>This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</string>
diff --git a/src/qt/forms/receivecoinsdialog.ui b/src/qt/forms/receivecoinsdialog.ui
index 0d280f2993..0214356eaa 100644
--- a/src/qt/forms/receivecoinsdialog.ui
+++ b/src/qt/forms/receivecoinsdialog.ui
@@ -189,7 +189,7 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="useLegacyAddress">
+ <widget class="QCheckBox" name="useBech32">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -206,10 +206,10 @@
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
- <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When checked, an address compatible with older wallets will be created instead.</string>
+ <string>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don't support them. When unchecked, an address compatible with older wallets will be created instead.</string>
</property>
<property name="text">
- <string>Generate legacy address</string>
+ <string>Generate native segwit (Bech32) address</string>
</property>
</widget>
</item>
@@ -360,7 +360,7 @@
<tabstops>
<tabstop>reqLabel</tabstop>
<tabstop>reqAmount</tabstop>
- <tabstop>useLegacyAddress</tabstop>
+ <tabstop>useBech32</tabstop>
<tabstop>reqMessage</tabstop>
<tabstop>receiveButton</tabstop>
<tabstop>clearButton</tabstop>
diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h
index d8f5594983..dcdb247977 100644
--- a/src/qt/guiconstants.h
+++ b/src/qt/guiconstants.h
@@ -5,6 +5,8 @@
#ifndef BITCOIN_QT_GUICONSTANTS_H
#define BITCOIN_QT_GUICONSTANTS_H
+#include <cstdint>
+
/* Milliseconds between model updates */
static const int MODEL_UPDATE_DELAY = 250;
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 070df31aa6..c4e0321f28 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -588,7 +588,7 @@ bool SetStartOnSystemStartup(bool fAutoStart)
// Start client minimized
QString strArgs = "-min";
// Set -testnet /-regtest options
- strArgs += QString::fromStdString(strprintf(" -testnet=%d -regtest=%d", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false)));
+ strArgs += QString::fromStdString(strprintf(" -chain=%s", gArgs.GetChainName()));
// Set the path to the shortcut target
psl->SetPath(pszExePath);
@@ -683,7 +683,7 @@ bool SetStartOnSystemStartup(bool fAutoStart)
optionFile << "Name=Bitcoin\n";
else
optionFile << strprintf("Name=Bitcoin (%s)\n", chain);
- optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", gArgs.GetBoolArg("-testnet", false), gArgs.GetBoolArg("-regtest", false));
+ optionFile << "Exec=" << pszExePath << strprintf(" -min -chain=%s\n", chain);
optionFile << "Terminal=false\n";
optionFile << "Hidden=false\n";
optionFile.close();
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
index 102e37e471..9e05c63aa0 100644
--- a/src/qt/intro.cpp
+++ b/src/qt/intro.cpp
@@ -131,6 +131,11 @@ Intro::Intro(QWidget *parent, uint64_t blockchain_size, uint64_t chain_state_siz
ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME));
uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0));
+ if (pruneTarget > 1) { // -prune=1 means enabled, above that it's a size in MB
+ ui->prune->setChecked(true);
+ ui->prune->setEnabled(false);
+ }
+ ui->prune->setText(tr("Discard blocks after verification, except most recent %1 GB (prune)").arg(pruneTarget ? pruneTarget / 1000 : 2));
requiredSpace = m_blockchain_size;
QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
if (pruneTarget) {
@@ -180,8 +185,10 @@ void Intro::setDataDirectory(const QString &dataDir)
}
}
-bool Intro::pickDataDirectory(interfaces::Node& node)
+bool Intro::showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune)
{
+ did_show_intro = false;
+
QSettings settings;
/* If data directory provided on command line, no need to look at settings
or show a picking dialog */
@@ -205,6 +212,7 @@ bool Intro::pickDataDirectory(interfaces::Node& node)
Intro intro(0, node.getAssumedBlockchainSize(), node.getAssumedChainStateSize());
intro.setDataDirectory(dataDir);
intro.setWindowIcon(QIcon(":icons/bitcoin"));
+ did_show_intro = true;
while(true)
{
@@ -227,6 +235,9 @@ bool Intro::pickDataDirectory(interfaces::Node& node)
}
}
+ // Additional preferences:
+ prune = intro.ui->prune->isChecked();
+
settings.setValue("strDataDir", dataDir);
settings.setValue("fReset", false);
}
@@ -263,6 +274,11 @@ void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable
{
freeString += " " + tr("(of %n GB needed)", "", requiredSpace);
ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
+ ui->prune->setChecked(true);
+ } else if (bytesAvailable / GB_BYTES - requiredSpace < 10) {
+ freeString += " " + tr("(%n GB needed for full chain)", "", requiredSpace);
+ ui->freeSpace->setStyleSheet("QLabel { color: #999900 }");
+ ui->prune->setChecked(true);
} else {
ui->freeSpace->setStyleSheet("");
}
diff --git a/src/qt/intro.h b/src/qt/intro.h
index c3b26808d4..aca7e71642 100644
--- a/src/qt/intro.h
+++ b/src/qt/intro.h
@@ -39,6 +39,7 @@ public:
/**
* Determine data directory. Let the user choose if the current one doesn't exist.
+ * Let the user configure additional preferences such as pruning.
*
* @returns true if a data directory was selected, false if the user cancelled the selection
* dialog.
@@ -46,7 +47,7 @@ public:
* @note do NOT call global GetDataDir() before calling this function, this
* will cause the wrong path to be cached.
*/
- static bool pickDataDirectory(interfaces::Node& node);
+ static bool showIfNeeded(interfaces::Node& node, bool& did_show_intro, bool& prune);
Q_SIGNALS:
void requestCheck();
diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts
index 7864f97f31..d34fd9eb45 100644
--- a/src/qt/locale/bitcoin_en.ts
+++ b/src/qt/locale/bitcoin_en.ts
@@ -171,16 +171,11 @@
</message>
<message>
<location line="+14"/>
- <source>Show password</source>
+ <source>Show passphrase</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../askpassphrasedialog.cpp" line="+46"/>
- <source>Enter the new passphrase to the wallet.&lt;br/&gt;Please use a passphrase of &lt;b&gt;ten or more random characters&lt;/b&gt;, or &lt;b&gt;eight or more words&lt;/b&gt;.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+3"/>
+ <location filename="../askpassphrasedialog.cpp" line="+50"/>
<source>Encrypt wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -210,12 +205,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+1"/>
- <source>Enter the old passphrase and new passphrase to the wallet.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+45"/>
+ <location line="+46"/>
<source>Confirm wallet encryption</source>
<translation type="unfinished"></translation>
</message>
@@ -230,36 +220,61 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+9"/>
- <location line="+58"/>
+ <location line="+19"/>
+ <location line="+57"/>
<source>Wallet encrypted</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-56"/>
- <source>Your wallet is now encrypted. Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer.</source>
+ <location line="-145"/>
+ <source>Enter the new passphrase for the wallet.&lt;br/&gt;Please use a passphrase of &lt;b&gt;ten or more random characters&lt;/b&gt;, or &lt;b&gt;eight or more words&lt;/b&gt;.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+23"/>
+ <source>Enter the old passphrase and new passphrase for the wallet.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+53"/>
+ <source>Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+4"/>
+ <source>Wallet to be encrypted</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Your wallet is about to be encrypted. </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+8"/>
+ <source>Your wallet is now encrypted. </source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
<source>IMPORTANT: Any previous backups you have made of your wallet file should be replaced with the newly generated, encrypted wallet file. For security reasons, previous backups of the unencrypted wallet file will become useless as soon as you start using the new, encrypted wallet.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+8"/>
- <location line="+7"/>
+ <location line="+8"/>
<location line="+43"/>
<location line="+6"/>
<source>Wallet encryption failed</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-55"/>
+ <location line="-56"/>
<source>Wallet encryption failed due to an internal error. Your wallet was not encrypted.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+7"/>
+ <location line="+8"/>
<location line="+49"/>
<source>The supplied passphrases do not match.</source>
<translation type="unfinished"></translation>
@@ -310,17 +325,17 @@
<context>
<name>BitcoinGUI</name>
<message>
- <location filename="../bitcoingui.cpp" line="+315"/>
+ <location filename="../bitcoingui.cpp" line="+316"/>
<source>Sign &amp;message...</source>
<translation>Sign &amp;message...</translation>
</message>
<message>
- <location line="+637"/>
+ <location line="+623"/>
<source>Synchronizing with network...</source>
<translation>Synchronizing with network...</translation>
</message>
<message>
- <location line="-715"/>
+ <location line="-701"/>
<source>&amp;Overview</source>
<translation>&amp;Overview</translation>
</message>
@@ -400,7 +415,17 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+216"/>
+ <location line="+11"/>
+ <source>Create Wallet...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Create a new wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+190"/>
<source>Wallet:</source>
<translation type="unfinished"></translation>
</message>
@@ -435,7 +460,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-1035"/>
+ <location line="-1021"/>
<source>Send coins to a Bitcoin address</source>
<translation>Send coins to a Bitcoin address</translation>
</message>
@@ -500,17 +525,17 @@
<translation>Verify messages to ensure they were signed with specified Bitcoin addresses</translation>
</message>
<message>
- <location line="+117"/>
+ <location line="+110"/>
<source>&amp;File</source>
<translation>&amp;File</translation>
</message>
<message>
- <location line="+14"/>
+ <location line="+15"/>
<source>&amp;Settings</source>
<translation>&amp;Settings</translation>
</message>
<message>
- <location line="+66"/>
+ <location line="+58"/>
<source>&amp;Help</source>
<translation>&amp;Help</translation>
</message>
@@ -520,7 +545,7 @@
<translation>Tabs toolbar</translation>
</message>
<message>
- <location line="-270"/>
+ <location line="-256"/>
<source>Request payments (generates QR codes and bitcoin: URIs)</source>
<translation type="unfinished"></translation>
</message>
@@ -540,12 +565,12 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+10"/>
+ <location line="+13"/>
<source>&amp;Command-line options</source>
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location line="+539"/>
+ <location line="+522"/>
<source>%n active connection(s) to Bitcoin network</source>
<translation>
<numerusform>%n active connection to Bitcoin network</numerusform>
@@ -606,7 +631,7 @@
<translation>Up to date</translation>
</message>
<message>
- <location line="-656"/>
+ <location line="-642"/>
<source>&amp;Sending addresses</source>
<translation type="unfinished"></translation>
</message>
@@ -636,7 +661,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+4"/>
+ <location line="+7"/>
<source>Show the %1 help message to get a list with possible Bitcoin command-line options</source>
<translation type="unfinished"></translation>
</message>
@@ -646,22 +671,12 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+13"/>
- <source>Opening Wallet &lt;b&gt;%1&lt;/b&gt;...</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+9"/>
- <source>Open Wallet Failed</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+15"/>
+ <location line="+21"/>
<source>No wallets available</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+48"/>
+ <location line="+55"/>
<source>&amp;Window</source>
<translation type="unfinished">&amp;Window</translation>
</message>
@@ -676,12 +691,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+14"/>
- <source>Restore</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+12"/>
+ <location line="+18"/>
<source>Main Window</source>
<translation type="unfinished"></translation>
</message>
@@ -782,7 +792,7 @@
<translation>Wallet is &lt;b&gt;encrypted&lt;/b&gt; and currently &lt;b&gt;locked&lt;/b&gt;</translation>
</message>
<message>
- <location filename="../bitcoin.cpp" line="+382"/>
+ <location filename="../bitcoin.cpp" line="+386"/>
<source>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</source>
<translation type="unfinished"></translation>
</message>
@@ -978,6 +988,72 @@
</message>
</context>
<context>
+ <name>CreateWalletActivity</name>
+ <message>
+ <location filename="../walletcontroller.cpp" line="+201"/>
+ <source>Creating Wallet &lt;b&gt;%1&lt;/b&gt;...</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+26"/>
+ <source>Create wallet failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Create wallet warning</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>CreateWalletDialog</name>
+ <message>
+ <location filename="../forms/createwalletdialog.ui" line="+14"/>
+ <source>Create Wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+38"/>
+ <source>Wallet Name</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <source>Encrypt the wallet. The wallet will be encrypted with a passphrase of your choice.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Encrypt Wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+19"/>
+ <source>Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Disable Private Keys</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+13"/>
+ <source>Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Make Blank Wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="../createwalletdialog.cpp" line="+19"/>
+ <source>Create</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>EditAddressDialog</name>
<message>
<location filename="../forms/editaddressdialog.ui" line="+14"/>
@@ -1121,6 +1197,11 @@
</message>
<message>
<location line="+10"/>
+ <source>Reverting this setting requires re-downloading the entire blockchain. It is faster to download the full chain first and prune it later. Disables some advanced features.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
<source>This initial synchronisation is very demanding, and may expose hardware problems with your computer that had previously gone unnoticed. Each time you run %1, it will continue downloading where it left off.</source>
<translation type="unfinished"></translation>
</message>
@@ -1130,7 +1211,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-160"/>
+ <location line="-170"/>
<source>Use the default data directory</source>
<translation>Use the default data directory</translation>
</message>
@@ -1145,7 +1226,12 @@
<translation type="unfinished">Bitcoin</translation>
</message>
<message>
- <location line="+6"/>
+ <location line="+9"/>
+ <source>Discard blocks after verification, except most recent %1 GB (prune)</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
<source>At least %1 GB of data will be stored in this directory, and it will grow over time.</source>
<translation type="unfinished"></translation>
</message>
@@ -1165,12 +1251,12 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+75"/>
+ <location line="+78"/>
<source>Error: Specified data directory &quot;%1&quot; cannot be created.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+27"/>
+ <location line="+30"/>
<source>Error</source>
<translation>Error</translation>
</message>
@@ -1190,6 +1276,14 @@
<numerusform>(of %n GB needed)</numerusform>
</translation>
</message>
+ <message numerus="yes">
+ <location line="+4"/>
+ <source>(%n GB needed for full chain)</source>
+ <translation type="unfinished">
+ <numerusform></numerusform>
+ <numerusform></numerusform>
+ </translation>
+ </message>
</context>
<context>
<name>ModalOverlay</name>
@@ -1286,6 +1380,29 @@
</message>
</context>
<context>
+ <name>OpenWalletActivity</name>
+ <message>
+ <location filename="../walletcontroller.cpp" line="+39"/>
+ <source>Open wallet failed</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Open wallet warning</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+10"/>
+ <source>default wallet</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+2"/>
+ <source>Opening Wallet &lt;b&gt;%1&lt;/b&gt;...</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
<name>OptionsDialog</name>
<message>
<location filename="../forms/optionsdialog.ui" line="+14"/>
@@ -1734,7 +1851,7 @@
<name>PaymentServer</name>
<message>
<location filename="../paymentserver.cpp" line="+226"/>
- <location line="+346"/>
+ <location line="+350"/>
<location line="+42"/>
<location line="+108"/>
<location line="+14"/>
@@ -1743,7 +1860,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-527"/>
+ <location line="-531"/>
<source>Cannot start bitcoin: click-to-pay handler</source>
<translation type="unfinished"></translation>
</message>
@@ -1752,13 +1869,13 @@
<location line="+9"/>
<location line="+16"/>
<location line="+16"/>
- <location line="+5"/>
+ <location line="+7"/>
<location line="+7"/>
<source>URI handling</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-53"/>
+ <location line="-55"/>
<source>&apos;bitcoin://&apos; is not a valid URI. Use &apos;bitcoin:&apos; instead.</source>
<translation type="unfinished"></translation>
</message>
@@ -1774,12 +1891,24 @@
</message>
<message>
<location line="+16"/>
- <location line="+36"/>
+ <location line="+38"/>
<source>Cannot process payment request because BIP70 support was not compiled in.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-32"/>
+ <location line="-37"/>
+ <location line="+38"/>
+ <source>Due to widespread security flaws in BIP70 it&apos;s strongly recommended that any merchant instructions to switch wallets be ignored.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-37"/>
+ <location line="+38"/>
+ <source>If you are receiving this error you should request the merchant provide a BIP21 compatible URI.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="-34"/>
<source>Invalid payment address %1</source>
<translation type="unfinished"></translation>
</message>
@@ -1800,7 +1929,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+199"/>
+ <location line="+201"/>
<location line="+9"/>
<location line="+31"/>
<location line="+10"/>
@@ -2032,7 +2161,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../bitcoin.cpp" line="+116"/>
+ <location filename="../bitcoin.cpp" line="+118"/>
<source>Error: Specified data directory &quot;%1&quot; does not exist.</source>
<translation type="unfinished"></translation>
</message>
@@ -2047,7 +2176,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+59"/>
+ <location line="+64"/>
<source>%1 didn&apos;t yet exit safely...</source>
<translation type="unfinished"></translation>
</message>
@@ -2567,17 +2696,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+136"/>
- <source>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don&apos;t support them. When checked, an address compatible with older wallets will be created instead.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+3"/>
- <source>Generate legacy address</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="-178"/>
+ <location line="-39"/>
<location line="+153"/>
<source>An optional amount to request. Leave this empty or zero to not request a specific amount.</source>
<translation type="unfinished"></translation>
@@ -2598,7 +2717,17 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+142"/>
+ <location line="+78"/>
+ <source>Native segwit addresses (aka Bech32 or BIP-173) reduce your transaction fees later on and offer better protection against typos, but old wallets don&apos;t support them. When unchecked, an address compatible with older wallets will be created instead.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+3"/>
+ <source>Generate native segwit (Bech32) address</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+61"/>
<source>Requested payments history</source>
<translation type="unfinished"></translation>
</message>
@@ -3434,14 +3563,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
</message>
</context>
<context>
- <name>SplashScreen</name>
- <message>
- <location filename="../networkstyle.cpp" line="+19"/>
- <source>[testnet]</source>
- <translation>[testnet]</translation>
- </message>
-</context>
-<context>
<name>TrafficGraphWidget</name>
<message>
<location filename="../trafficgraphwidget.cpp" line="+81"/>
@@ -4036,13 +4157,13 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<context>
<name>WalletController</name>
<message>
- <location filename="../walletcontroller.cpp" line="+73"/>
+ <location filename="../walletcontroller.cpp" line="-205"/>
<source>Close wallet</source>
<translation type="unfinished"></translation>
</message>
<message>
<location line="+1"/>
- <source>Are you sure you wish to close wallet &lt;i&gt;%1&lt;/i&gt;?</source>
+ <source>Are you sure you wish to close the wallet &lt;i&gt;%1&lt;/i&gt;?</source>
<translation type="unfinished"></translation>
</message>
<message>
@@ -4410,12 +4531,22 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
+ <location line="+22"/>
+ <source>Unknown address type &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
+ <source>Unknown change type &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+4"/>
<source>Upgrading txindex database</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-44"/>
+ <location line="-46"/>
<source>Loading P2P addresses...</source>
<translation type="unfinished"></translation>
</message>
@@ -4475,7 +4606,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+4"/>
+ <location line="+6"/>
<source>Unsupported logging category %s=%s.</source>
<translation type="unfinished"></translation>
</message>
@@ -4500,7 +4631,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-154"/>
+ <location line="-156"/>
<source>Error: Listening for incoming connections failed (listen returned error %s)</source>
<translation type="unfinished"></translation>
</message>
@@ -4641,7 +4772,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+7"/>
+ <location line="+9"/>
<source>Verifying wallet(s)...</source>
<translation type="unfinished"></translation>
</message>
@@ -4656,7 +4787,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-177"/>
+ <location line="-179"/>
<source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source>
<translation type="unfinished"></translation>
</message>
@@ -4726,12 +4857,12 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+9"/>
+ <location line="+11"/>
<source>Unknown network specified in -onlynet: &apos;%s&apos;</source>
<translation>Unknown network specified in -onlynet: &apos;%s&apos;</translation>
</message>
<message>
- <location line="-50"/>
+ <location line="-52"/>
<source>Insufficient funds</source>
<translation>Insufficient funds</translation>
</message>
diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp
index f0c860e669..5c039a939e 100644
--- a/src/qt/networkstyle.cpp
+++ b/src/qt/networkstyle.cpp
@@ -6,6 +6,9 @@
#include <qt/guiconstants.h>
+#include <chainparamsbase.h>
+#include <tinyformat.h>
+
#include <QApplication>
static const struct {
@@ -13,11 +16,10 @@ static const struct {
const char *appName;
const int iconColorHueShift;
const int iconColorSaturationReduction;
- const char *titleAddText;
} network_styles[] = {
- {"main", QAPP_APP_NAME_DEFAULT, 0, 0, ""},
- {"test", QAPP_APP_NAME_TESTNET, 70, 30, QT_TRANSLATE_NOOP("SplashScreen", "[testnet]")},
- {"regtest", QAPP_APP_NAME_REGTEST, 160, 30, "[regtest]"}
+ {"main", QAPP_APP_NAME_DEFAULT, 0, 0},
+ {"test", QAPP_APP_NAME_TESTNET, 70, 30},
+ {"regtest", QAPP_APP_NAME_REGTEST, 160, 30}
};
static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles);
@@ -75,8 +77,9 @@ NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift,
trayAndWindowIcon = QIcon(pixmap.scaled(QSize(256,256)));
}
-const NetworkStyle *NetworkStyle::instantiate(const QString &networkId)
+const NetworkStyle* NetworkStyle::instantiate(const std::string& networkId)
{
+ std::string titleAddText = networkId == CBaseChainParams::MAIN ? "" : strprintf("[%s]", networkId);
for (unsigned x=0; x<network_styles_count; ++x)
{
if (networkId == network_styles[x].networkId)
@@ -85,7 +88,7 @@ const NetworkStyle *NetworkStyle::instantiate(const QString &networkId)
network_styles[x].appName,
network_styles[x].iconColorHueShift,
network_styles[x].iconColorSaturationReduction,
- network_styles[x].titleAddText);
+ titleAddText.c_str());
}
}
return nullptr;
diff --git a/src/qt/networkstyle.h b/src/qt/networkstyle.h
index b78a9f5948..bb12dd1b6e 100644
--- a/src/qt/networkstyle.h
+++ b/src/qt/networkstyle.h
@@ -14,7 +14,7 @@ class NetworkStyle
{
public:
/** Get style associated with provided BIP70 network id, or 0 if not known */
- static const NetworkStyle *instantiate(const QString &networkId);
+ static const NetworkStyle* instantiate(const std::string& networkId);
const QString &getAppName() const { return appName; }
const QIcon &getAppIcon() const { return appIcon; }
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index f3974b1c85..d047a82475 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -92,11 +92,7 @@ void OptionsModel::Init(bool resetSettings)
settings.setValue("bPrune", false);
if (!settings.contains("nPruneSize"))
settings.setValue("nPruneSize", 2);
- // Convert prune size from GB to MiB:
- const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20;
- if (!m_node.softSetArg("-prune", settings.value("bPrune").toBool() ? std::to_string(nPruneSizeMiB) : "0")) {
- addOverriddenOption("-prune");
- }
+ SetPrune(settings.value("bPrune").toBool());
if (!settings.contains("nDatabaseCache"))
settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache);
@@ -240,6 +236,22 @@ static const QString GetDefaultProxyAddress()
return QString("%1:%2").arg(DEFAULT_GUI_PROXY_HOST).arg(DEFAULT_GUI_PROXY_PORT);
}
+void OptionsModel::SetPrune(bool prune, bool force)
+{
+ QSettings settings;
+ settings.setValue("bPrune", prune);
+ // Convert prune size from GB to MiB:
+ const uint64_t nPruneSizeMiB = (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20;
+ std::string prune_val = prune ? std::to_string(nPruneSizeMiB) : "0";
+ if (force) {
+ m_node.forceSetArg("-prune", prune_val);
+ return;
+ }
+ if (!m_node.softSetArg("-prune", prune_val)) {
+ addOverriddenOption("-prune");
+ }
+}
+
// read QSettings values and return them
QVariant OptionsModel::data(const QModelIndex & index, int role) const
{
diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h
index 1af3a72b92..b1231b7c7d 100644
--- a/src/qt/optionsmodel.h
+++ b/src/qt/optionsmodel.h
@@ -77,6 +77,9 @@ public:
bool getCoinControlFeatures() const { return fCoinControlFeatures; }
const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; }
+ /* Explicit setters */
+ void SetPrune(bool prune, bool force = false);
+
/* Restart flag helper */
void setRestartRequired(bool fRequired);
bool isRestartRequired() const;
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index 0bb87742e9..00d83d23dd 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -328,7 +328,9 @@ void PaymentServer::handleURIOrFile(const QString& s)
#ifndef ENABLE_BIP70
if (uri.hasQueryItem("r")) { // payment request
Q_EMIT message(tr("URI handling"),
- tr("Cannot process payment request because BIP70 support was not compiled in."),
+ tr("Cannot process payment request because BIP70 support was not compiled in.")+
+ tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
+ tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
CClientUIInterface::ICON_WARNING);
}
#endif
@@ -364,7 +366,9 @@ void PaymentServer::handleURIOrFile(const QString& s)
return;
#else
Q_EMIT message(tr("Payment request file handling"),
- tr("Cannot process payment request because BIP70 support was not compiled in."),
+ tr("Cannot process payment request because BIP70 support was not compiled in.")+
+ tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
+ tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
CClientUIInterface::ICON_WARNING);
#endif
}
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index e8cf432131..df8d5115d5 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -96,13 +96,13 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
if (model->node().isAddressTypeSet()) {
// user explicitly set the type, use it
if (model->wallet().getDefaultAddressType() == OutputType::BECH32) {
- ui->useLegacyAddress->setCheckState(Qt::Unchecked);
+ ui->useBech32->setCheckState(Qt::Checked);
} else {
- ui->useLegacyAddress->setCheckState(Qt::Checked);
+ ui->useBech32->setCheckState(Qt::Unchecked);
}
} else {
// Always fall back to bech32 in the gui
- ui->useLegacyAddress->setCheckState(Qt::Unchecked);
+ ui->useBech32->setCheckState(Qt::Checked);
}
// Set the button to be enabled or disabled based on whether the wallet can give out new addresses.
@@ -155,7 +155,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked()
QString label = ui->reqLabel->text();
/* Generate new receiving address */
OutputType address_type;
- if (!ui->useLegacyAddress->isChecked()) {
+ if (ui->useBech32->isChecked()) {
address_type = OutputType::BECH32;
} else {
address_type = model->wallet().getDefaultAddressType();
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index f23c47736f..a88119d8c5 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -283,7 +283,7 @@ void SendCoinsDialog::on_sendButton_clicked()
// generate amount string with wallet name in case of multiwallet
QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
if (model->isMultiwallet()) {
- amount.append(tr(" from wallet '%1'").arg(model->getWalletName()));
+ amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
}
// generate address string
@@ -297,7 +297,7 @@ void SendCoinsDialog::on_sendButton_clicked()
{
if(rcp.label.length() > 0) // label with address
{
- recipientElement.append(tr("%1 to '%2'").arg(amount, rcp.label));
+ recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
recipientElement.append(QString(" (%1)").arg(address));
}
else // just address
diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp
index 49e9e072a8..8ae01ac093 100644
--- a/src/qt/test/apptests.cpp
+++ b/src/qt/test/apptests.cpp
@@ -68,8 +68,7 @@ void AppTests::appTests()
m_app.parameterSetup();
m_app.createOptionsModel(true /* reset settings */);
- QScopedPointer<const NetworkStyle> style(
- NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString())));
+ QScopedPointer<const NetworkStyle> style(NetworkStyle::instantiate(Params().NetworkIDString()));
m_app.setupPlatformStyle();
m_app.createWindow(style.data());
connect(&m_app, &BitcoinApplication::windowShown, this, &AppTests::guiTests);
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index a8e7bce6b5..fa6f9f3f16 100644
--- a/src/qt/walletcontroller.cpp
+++ b/src/qt/walletcontroller.cpp
@@ -2,8 +2,14 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <qt/askpassphrasedialog.h>
+#include <qt/createwalletdialog.h>
+#include <qt/guiconstants.h>
+#include <qt/guiutil.h>
#include <qt/walletcontroller.h>
+#include <wallet/wallet.h>
+
#include <interfaces/handler.h>
#include <interfaces/node.h>
@@ -13,10 +19,13 @@
#include <QMessageBox>
#include <QMutexLocker>
#include <QThread>
+#include <QTimer>
#include <QWindow>
WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent)
: QObject(parent)
+ , m_activity_thread(new QThread(this))
+ , m_activity_worker(new QObject)
, m_node(node)
, m_platform_style(platform_style)
, m_options_model(options_model)
@@ -29,15 +38,17 @@ WalletController::WalletController(interfaces::Node& node, const PlatformStyle*
getOrCreateWallet(std::move(wallet));
}
- m_activity_thread.start();
+ m_activity_worker->moveToThread(m_activity_thread);
+ m_activity_thread->start();
}
// Not using the default destructor because not all member types definitions are
// available in the header, just forward declared.
WalletController::~WalletController()
{
- m_activity_thread.quit();
- m_activity_thread.wait();
+ m_activity_thread->quit();
+ m_activity_thread->wait();
+ delete m_activity_worker;
}
std::vector<WalletModel*> WalletController::getOpenWallets() const
@@ -60,18 +71,11 @@ std::map<std::string, bool> WalletController::listWalletDir() const
return wallets;
}
-OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent)
-{
- OpenWalletActivity* activity = new OpenWalletActivity(this, name);
- activity->moveToThread(&m_activity_thread);
- return activity;
-}
-
void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
{
QMessageBox box(parent);
box.setWindowTitle(tr("Close wallet"));
- box.setText(tr("Are you sure you wish to close wallet <i>%1</i>?").arg(wallet_model->getDisplayName()));
+ box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
box.setDefaultButton(QMessageBox::Yes);
@@ -140,23 +144,148 @@ void WalletController::removeAndDeleteWallet(WalletModel* wallet_model)
delete wallet_model;
}
+WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget)
+ : QObject(wallet_controller)
+ , m_wallet_controller(wallet_controller)
+ , m_parent_widget(parent_widget)
+{
+}
-OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name)
- : m_wallet_controller(wallet_controller)
- , m_name(name)
-{}
+WalletControllerActivity::~WalletControllerActivity()
+{
+ delete m_progress_dialog;
+}
-void OpenWalletActivity::open()
+void WalletControllerActivity::showProgressDialog(const QString& label_text)
{
- std::string error, warning;
- std::unique_ptr<interfaces::Wallet> wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning);
- if (!warning.empty()) {
- Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning));
+ m_progress_dialog = new QProgressDialog(m_parent_widget);
+
+ m_progress_dialog->setLabelText(label_text);
+ m_progress_dialog->setRange(0, 0);
+ m_progress_dialog->setCancelButton(nullptr);
+ m_progress_dialog->setWindowModality(Qt::ApplicationModal);
+ GUIUtil::PolishProgressDialog(m_progress_dialog);
+}
+
+CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
+ : WalletControllerActivity(wallet_controller, parent_widget)
+{
+ m_passphrase.reserve(MAX_PASSPHRASE_SIZE);
+}
+
+CreateWalletActivity::~CreateWalletActivity()
+{
+ delete m_create_wallet_dialog;
+ delete m_passphrase_dialog;
+}
+
+void CreateWalletActivity::askPassphrase()
+{
+ m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase);
+ m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
+ m_passphrase_dialog->show();
+
+ connect(m_passphrase_dialog, &QObject::destroyed, [this] {
+ m_passphrase_dialog = nullptr;
+ });
+ connect(m_passphrase_dialog, &QDialog::accepted, [this] {
+ createWallet();
+ });
+ connect(m_passphrase_dialog, &QDialog::rejected, [this] {
+ Q_EMIT finished();
+ });
+}
+
+void CreateWalletActivity::createWallet()
+{
+ showProgressDialog(tr("Creating Wallet <b>%1</b>...").arg(m_create_wallet_dialog->walletName().toHtmlEscaped()));
+
+ std::string name = m_create_wallet_dialog->walletName().toStdString();
+ uint64_t flags = 0;
+ if (m_create_wallet_dialog->isDisablePrivateKeysChecked()) {
+ flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
}
- if (wallet) {
- Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet)));
- } else {
- Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error));
+ if (m_create_wallet_dialog->isMakeBlankWalletChecked()) {
+ flags |= WALLET_FLAG_BLANK_WALLET;
}
+
+ QTimer::singleShot(500, worker(), [this, name, flags] {
+ std::unique_ptr<interfaces::Wallet> wallet;
+ WalletCreationStatus status = node().createWallet(m_passphrase, flags, name, m_error_message, m_warning_message, wallet);
+
+ if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
+
+ QTimer::singleShot(500, this, &CreateWalletActivity::finish);
+ });
+}
+
+void CreateWalletActivity::finish()
+{
+ m_progress_dialog->hide();
+
+ if (!m_error_message.empty()) {
+ QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message));
+ } else if (!m_warning_message.empty()) {
+ QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(m_warning_message));
+ }
+
+ if (m_wallet_model) Q_EMIT created(m_wallet_model);
+
+ Q_EMIT finished();
+}
+
+void CreateWalletActivity::create()
+{
+ m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
+ m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
+ m_create_wallet_dialog->show();
+
+ connect(m_create_wallet_dialog, &QObject::destroyed, [this] {
+ m_create_wallet_dialog = nullptr;
+ });
+ connect(m_create_wallet_dialog, &QDialog::rejected, [this] {
+ Q_EMIT finished();
+ });
+ connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
+ if (m_create_wallet_dialog->isEncryptWalletChecked()) {
+ askPassphrase();
+ } else {
+ createWallet();
+ }
+ });
+}
+
+OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
+ : WalletControllerActivity(wallet_controller, parent_widget)
+{
+}
+
+void OpenWalletActivity::finish()
+{
+ m_progress_dialog->hide();
+
+ if (!m_error_message.empty()) {
+ QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message));
+ } else if (!m_warning_message.empty()) {
+ QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(m_warning_message));
+ }
+
+ if (m_wallet_model) Q_EMIT opened(m_wallet_model);
+
Q_EMIT finished();
}
+
+void OpenWalletActivity::open(const std::string& path)
+{
+ QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);
+
+ showProgressDialog(tr("Opening Wallet <b>%1</b>...").arg(name.toHtmlEscaped()));
+
+ QTimer::singleShot(0, worker(), [this, path] {
+ std::unique_ptr<interfaces::Wallet> wallet = node().loadWallet(path, m_error_message, m_warning_message);
+
+ if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet));
+
+ QTimer::singleShot(0, this, &OpenWalletActivity::finish);
+ });
+}
diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h
index be1c282919..fb37b7292c 100644
--- a/src/qt/walletcontroller.h
+++ b/src/qt/walletcontroller.h
@@ -6,15 +6,20 @@
#define BITCOIN_QT_WALLETCONTROLLER_H
#include <qt/walletmodel.h>
+#include <support/allocators/secure.h>
#include <sync.h>
#include <map>
#include <memory>
+#include <string>
#include <vector>
#include <QMessageBox>
#include <QMutex>
+#include <QProgressDialog>
#include <QThread>
+#include <QTimer>
+#include <QString>
class OptionsModel;
class PlatformStyle;
@@ -24,7 +29,11 @@ class Handler;
class Node;
} // namespace interfaces
+class AskPassphraseDialog;
+class CreateWalletActivity;
+class CreateWalletDialog;
class OpenWalletActivity;
+class WalletControllerActivity;
/**
* Controller between interfaces::Node, WalletModel instances and the GUI.
@@ -33,7 +42,6 @@ class WalletController : public QObject
{
Q_OBJECT
- WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet);
void removeAndDeleteWallet(WalletModel* wallet_model);
public:
@@ -43,11 +51,12 @@ public:
//! Returns wallet models currently open.
std::vector<WalletModel*> getOpenWallets() const;
+ WalletModel* getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet);
+
//! Returns all wallet names in the wallet dir mapped to whether the wallet
//! is loaded.
std::map<std::string, bool> listWalletDir() const;
- OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr);
void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr);
Q_SIGNALS:
@@ -57,7 +66,8 @@ Q_SIGNALS:
void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction);
private:
- QThread m_activity_thread;
+ QThread* const m_activity_thread;
+ QObject* const m_activity_worker;
interfaces::Node& m_node;
const PlatformStyle* const m_platform_style;
OptionsModel* const m_options_model;
@@ -65,27 +75,72 @@ private:
std::vector<WalletModel*> m_wallets;
std::unique_ptr<interfaces::Handler> m_handler_load_wallet;
- friend class OpenWalletActivity;
+ friend class WalletControllerActivity;
};
-class OpenWalletActivity : public QObject
+class WalletControllerActivity : public QObject
{
Q_OBJECT
public:
- OpenWalletActivity(WalletController* wallet_controller, const std::string& name);
-
-public Q_SLOTS:
- void open();
+ WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget);
+ virtual ~WalletControllerActivity();
Q_SIGNALS:
- void message(QMessageBox::Icon icon, const QString text);
void finished();
+
+protected:
+ interfaces::Node& node() const { return m_wallet_controller->m_node; }
+ QObject* worker() const { return m_wallet_controller->m_activity_worker; }
+
+ void showProgressDialog(const QString& label_text);
+
+ WalletController* const m_wallet_controller;
+ QWidget* const m_parent_widget;
+ QProgressDialog* m_progress_dialog{nullptr};
+ WalletModel* m_wallet_model{nullptr};
+ std::string m_error_message;
+ std::string m_warning_message;
+};
+
+
+class CreateWalletActivity : public WalletControllerActivity
+{
+ Q_OBJECT
+
+public:
+ CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget);
+ virtual ~CreateWalletActivity();
+
+ void create();
+
+Q_SIGNALS:
+ void created(WalletModel* wallet_model);
+
+private:
+ void askPassphrase();
+ void createWallet();
+ void finish();
+
+ SecureString m_passphrase;
+ CreateWalletDialog* m_create_wallet_dialog{nullptr};
+ AskPassphraseDialog* m_passphrase_dialog{nullptr};
+};
+
+class OpenWalletActivity : public WalletControllerActivity
+{
+ Q_OBJECT
+
+public:
+ OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget);
+
+ void open(const std::string& path);
+
+Q_SIGNALS:
void opened(WalletModel* wallet_model);
private:
- WalletController* const m_wallet_controller;
- std::string const m_name;
+ void finish();
};
#endif // BITCOIN_QT_WALLETCONTROLLER_H
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index be47f67f95..8652827b59 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -170,9 +170,9 @@ void WalletView::processNewTransaction(const QModelIndex& parent, int start, int
QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString();
QModelIndex index = ttm->index(start, 0, parent);
QString address = ttm->data(index, TransactionTableModel::AddressRole).toString();
- QString label = ttm->data(index, TransactionTableModel::LabelRole).toString();
+ QString label = GUIUtil::HtmlEscape(ttm->data(index, TransactionTableModel::LabelRole).toString());
- Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, walletModel->getWalletName());
+ Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName()));
}
void WalletView::gotoOverviewPage()
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 5419e33396..02717fa80f 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1159,7 +1159,7 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam
{
bip9.pushKV("bit", consensusParams.vDeployments[id].bit);
}
- bip9.pushKV("startTime", consensusParams.vDeployments[id].nStartTime);
+ bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime);
bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout);
int64_t since_height = VersionBitsTipStateSinceHeight(consensusParams, id);
bip9.pushKV("since", since_height);
@@ -1213,7 +1213,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
" \"bip9\": { (object) status of bip9 softforks (only for \"bip9\" type)\n"
" \"status\": \"xxxx\", (string) one of \"defined\", \"started\", \"locked_in\", \"active\", \"failed\"\n"
" \"bit\": xx, (numeric) the bit (0-28) in the block version field used to signal this softfork (only for \"started\" status)\n"
- " \"startTime\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
+ " \"start_time\": xx, (numeric) the minimum median time past of a block at which the bit gains its meaning\n"
" \"timeout\": xx, (numeric) the median time past of a block at which the deployment is considered failed if not yet locked in\n"
" \"since\": xx, (numeric) height of the first block to which the status applies\n"
" \"statistics\": { (object) numeric statistics about BIP9 signalling for a softfork\n"
@@ -2064,17 +2064,21 @@ UniValue scantxoutset(const JSONRPCRequest& request)
},
RPCResult{
"{\n"
+ " \"success\": true|false, (boolean) Whether the scan was completed\n"
+ " \"txouts\": n, (numeric) The number of unspent transaction outputs scanned\n"
+ " \"height\": n, (numeric) The current block height (index)\n"
+ " \"bestblock\": \"hex\", (string) The hash of the block at the tip of the chain\n"
" \"unspents\": [\n"
- " {\n"
- " \"txid\" : \"transactionid\", (string) The transaction id\n"
- " \"vout\": n, (numeric) the vout value\n"
- " \"scriptPubKey\" : \"script\", (string) the script key\n"
- " \"desc\" : \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n"
- " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
- " \"height\" : n, (numeric) Height of the unspent transaction output\n"
+ " {\n"
+ " \"txid\": \"hash\", (string) The transaction id\n"
+ " \"vout\": n, (numeric) The vout value\n"
+ " \"scriptPubKey\": \"script\", (string) The script key\n"
+ " \"desc\": \"descriptor\", (string) A specialized descriptor for the matched scriptPubKey\n"
+ " \"amount\": x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
+ " \"height\": n, (numeric) Height of the unspent transaction output\n"
" }\n"
- " ,...], \n"
- " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
+ " ,...],\n"
+ " \"total_amount\": x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
"]\n"
},
RPCExamples{""},
@@ -2128,15 +2132,20 @@ UniValue scantxoutset(const JSONRPCRequest& request)
g_scan_progress = 0;
int64_t count = 0;
std::unique_ptr<CCoinsViewCursor> pcursor;
+ CBlockIndex* tip;
{
LOCK(cs_main);
::ChainstateActive().ForceFlushStateToDisk();
pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor());
assert(pcursor);
+ tip = ::ChainActive().Tip();
+ assert(tip);
}
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
result.pushKV("success", res);
- result.pushKV("searched_items", count);
+ result.pushKV("txouts", count);
+ result.pushKV("height", tip->nHeight);
+ result.pushKV("bestblock", tip->GetBlockHash().GetHex());
for (const auto& it : coins) {
const COutPoint& outpoint = it.first;
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 93fca5a6de..c2714f9c83 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -85,7 +85,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getblockheader", 1, "verbose" },
{ "getchaintxstats", 0, "nblocks" },
{ "gettransaction", 1, "include_watchonly" },
- { "gettransaction", 2, "decode" },
+ { "gettransaction", 2, "verbose" },
{ "getrawtransaction", 1, "verbose" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index 25dda924a4..7c4b3d0cc6 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -82,6 +82,10 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
" \"addrbind\":\"ip:port\", (string) Bind address of the connection to the peer\n"
" \"addrlocal\":\"ip:port\", (string) Local address as reported by the peer\n"
" \"services\":\"xxxxxxxxxxxxxxxx\", (string) The services offered\n"
+ " \"servicesnames\":[ (array) the services offered, in human-readable form\n"
+ " \"SERVICE_NAME\", (string) the service name if it is recognised\n"
+ " ...\n"
+ " ],\n"
" \"relaytxes\":true|false, (boolean) Whether peer has asked us to relay transactions to it\n"
" \"lastsend\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last send\n"
" \"lastrecv\": ttt, (numeric) The time in seconds since epoch (Jan 1 1970 GMT) of the last receive\n"
@@ -147,6 +151,7 @@ static UniValue getpeerinfo(const JSONRPCRequest& request)
if (stats.addrBind.IsValid())
obj.pushKV("addrbind", stats.addrBind.ToString());
obj.pushKV("services", strprintf("%016x", stats.nServices));
+ obj.pushKV("servicesnames", GetServicesNames(stats.nServices));
obj.pushKV("relaytxes", stats.fRelayTxes);
obj.pushKV("lastsend", stats.nLastSend);
obj.pushKV("lastrecv", stats.nLastRecv);
@@ -446,6 +451,10 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
" \"subversion\": \"/Satoshi:x.x.x/\", (string) the server subversion string\n"
" \"protocolversion\": xxxxx, (numeric) the protocol version\n"
" \"localservices\": \"xxxxxxxxxxxxxxxx\", (string) the services we offer to the network\n"
+ " \"localservicesnames\": [ (array) the services we offer to the network, in human-readable form\n"
+ " \"SERVICE_NAME\", (string) the service name\n"
+ " ...\n"
+ " ],\n"
" \"localrelay\": true|false, (bool) true if transaction relay is requested from peers\n"
" \"timeoffset\": xxxxx, (numeric) the time offset\n"
" \"connections\": xxxxx, (numeric) the number of connections\n"
@@ -484,8 +493,11 @@ static UniValue getnetworkinfo(const JSONRPCRequest& request)
obj.pushKV("version", CLIENT_VERSION);
obj.pushKV("subversion", strSubVersion);
obj.pushKV("protocolversion",PROTOCOL_VERSION);
- if(g_connman)
- obj.pushKV("localservices", strprintf("%016x", g_connman->GetLocalServices()));
+ if (g_connman) {
+ ServiceFlags services = g_connman->GetLocalServices();
+ obj.pushKV("localservices", strprintf("%016x", services));
+ obj.pushKV("localservicesnames", GetServicesNames(services));
+ }
obj.pushKV("localrelay", g_relay_txes);
obj.pushKV("timeoffset", GetTimeOffset());
if (g_connman) {
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index ffbad45714..f548d356cf 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -14,9 +14,11 @@
#include <node/coin.h>
#include <node/psbt.h>
#include <node/transaction.h>
+#include <policy/policy.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <psbt.h>
+#include <random.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/util.h>
@@ -37,11 +39,11 @@
#include <univalue.h>
-/** High fee for sendrawtransaction and testmempoolaccept.
- * By default, transaction with a fee higher than this will be rejected by the
- * RPCs. This can be overridden with the maxfeerate argument.
+/** Maximum fee rate for sendrawtransaction and testmempoolaccept.
+ * By default, a transaction with a fee rate higher than this will be rejected
+ * by the RPCs. This can be overridden with the maxfeerate argument.
*/
-constexpr static CAmount DEFAULT_MAX_RAW_TX_FEE{COIN / 10};
+static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10};
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
{
@@ -758,7 +760,10 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
}
FindCoins(coins);
- return SignTransaction(mtx, request.params[2], &keystore, coins, true, request.params[3]);
+ // Parse the prevtxs array
+ ParsePrevouts(request.params[2], &keystore, coins);
+
+ return SignTransaction(mtx, &keystore, coins, request.params[3]);
}
static UniValue sendrawtransaction(const JSONRPCRequest& request)
@@ -771,7 +776,7 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
"\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
- {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE),
+ {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
"/kB.\nSet to 0 to accept any fee rate.\n"},
},
@@ -801,19 +806,17 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
- CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
+ CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE;
// TODO: temporary migration code for old clients. Remove in v0.20
if (request.params[1].isBool()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
} else if (!request.params[1].isNull()) {
- size_t weight = GetTransactionWeight(*tx);
- CFeeRate fr(AmountFromValue(request.params[1]));
- // the +3/4 part rounds the value up, and is the same formula used when
- // calculating the fee for a transaction
- // (see GetVirtualTransactionSize)
- max_raw_tx_fee = fr.GetFee((weight+3)/4);
+ max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1]));
}
+ int64_t virtual_size = GetVirtualTransactionSize(*tx);
+ CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+
std::string err_string;
AssertLockNotHeld(cs_main);
const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
@@ -837,7 +840,7 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
{"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
},
},
- {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
+ {"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()), "Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT + "/kB\n"},
},
RPCResult{
"[ (array) The result of the mempool acceptance test for each raw transaction in the input array.\n"
@@ -877,19 +880,17 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const uint256& tx_hash = tx->GetHash();
- CAmount max_raw_tx_fee = DEFAULT_MAX_RAW_TX_FEE;
+ CFeeRate max_raw_tx_fee_rate = DEFAULT_MAX_RAW_TX_FEE_RATE;
// TODO: temporary migration code for old clients. Remove in v0.20
if (request.params[1].isBool()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Second argument must be numeric (maxfeerate) and no longer supports a boolean. To allow a transaction with high fees, set maxfeerate to 0.");
} else if (!request.params[1].isNull()) {
- size_t weight = GetTransactionWeight(*tx);
- CFeeRate fr(AmountFromValue(request.params[1]));
- // the +3/4 part rounds the value up, and is the same formula used when
- // calculating the fee for a transaction
- // (see GetVirtualTransactionSize)
- max_raw_tx_fee = fr.GetFee((weight+3)/4);
+ max_raw_tx_fee_rate = CFeeRate(AmountFromValue(request.params[1]));
}
+ int64_t virtual_size = GetVirtualTransactionSize(*tx);
+ CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
+
UniValue result(UniValue::VARR);
UniValue result_0(UniValue::VOBJ);
result_0.pushKV("txid", tx_hash.GetHex());
@@ -1612,8 +1613,30 @@ UniValue joinpsbts(const JSONRPCRequest& request)
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
+ // Generate list of shuffled indices for shuffling inputs and outputs of the merged PSBT
+ std::vector<int> input_indices(merged_psbt.inputs.size());
+ std::iota(input_indices.begin(), input_indices.end(), 0);
+ std::vector<int> output_indices(merged_psbt.outputs.size());
+ std::iota(output_indices.begin(), output_indices.end(), 0);
+
+ // Shuffle input and output indicies lists
+ Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
+ Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
+
+ PartiallySignedTransaction shuffled_psbt;
+ shuffled_psbt.tx = CMutableTransaction();
+ shuffled_psbt.tx->nVersion = merged_psbt.tx->nVersion;
+ shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
+ for (int i : input_indices) {
+ shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
+ }
+ for (int i : output_indices) {
+ shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]);
+ }
+ shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- ssTx << merged_psbt;
+ ssTx << shuffled_psbt;
return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size());
}
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index 55425cca35..fe98fff4bb 100644
--- a/src/rpc/rawtransaction_util.cpp
+++ b/src/rpc/rawtransaction_util.cpp
@@ -147,9 +147,8 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
vErrorsRet.push_back(entry);
}
-UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool is_temp_keystore, const UniValue& hashType)
+void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
{
- // Add previous txouts given in the RPC call:
if (!prevTxsUnival.isNull()) {
UniValue prevTxs = prevTxsUnival.get_array();
for (unsigned int idx = 0; idx < prevTxs.size(); ++idx) {
@@ -197,36 +196,80 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
}
// if redeemScript and private keys were given, add redeemScript to the keystore so it can be signed
- if (is_temp_keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
+ const bool is_p2sh = scriptPubKey.IsPayToScriptHash();
+ const bool is_p2wsh = scriptPubKey.IsPayToWitnessScriptHash();
+ if (keystore && (is_p2sh || is_p2wsh)) {
RPCTypeCheckObj(prevOut,
{
{"redeemScript", UniValueType(UniValue::VSTR)},
{"witnessScript", UniValueType(UniValue::VSTR)},
}, true);
UniValue rs = find_value(prevOut, "redeemScript");
- if (!rs.isNull()) {
- std::vector<unsigned char> rsData(ParseHexV(rs, "redeemScript"));
- CScript redeemScript(rsData.begin(), rsData.end());
- keystore->AddCScript(redeemScript);
- // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
- // This is only for compatibility, it is encouraged to use the explicit witnessScript field instead.
- keystore->AddCScript(GetScriptForWitness(redeemScript));
- }
UniValue ws = find_value(prevOut, "witnessScript");
- if (!ws.isNull()) {
- std::vector<unsigned char> wsData(ParseHexV(ws, "witnessScript"));
- CScript witnessScript(wsData.begin(), wsData.end());
- keystore->AddCScript(witnessScript);
- // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
- keystore->AddCScript(GetScriptForWitness(witnessScript));
- }
if (rs.isNull() && ws.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing redeemScript/witnessScript");
}
+
+ // work from witnessScript when possible
+ std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript"));
+ CScript script(scriptData.begin(), scriptData.end());
+ keystore->AddCScript(script);
+ // Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
+ // This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
+ CScript witness_output_script{GetScriptForWitness(script)};
+ keystore->AddCScript(witness_output_script);
+
+ if (!ws.isNull() && !rs.isNull()) {
+ // if both witnessScript and redeemScript are provided,
+ // they should either be the same (for backwards compat),
+ // or the redeemScript should be the encoded form of
+ // the witnessScript (ie, for p2sh-p2wsh)
+ if (ws.get_str() != rs.get_str()) {
+ std::vector<unsigned char> redeemScriptData(ParseHexV(rs, "redeemScript"));
+ CScript redeemScript(redeemScriptData.begin(), redeemScriptData.end());
+ if (redeemScript != witness_output_script) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript does not correspond to witnessScript");
+ }
+ }
+ }
+
+ if (is_p2sh) {
+ const CTxDestination p2sh{ScriptHash(script)};
+ const CTxDestination p2sh_p2wsh{ScriptHash(witness_output_script)};
+ if (scriptPubKey == GetScriptForDestination(p2sh)) {
+ // traditional p2sh; arguably an error if
+ // we got here with rs.IsNull(), because
+ // that means the p2sh script was specified
+ // via witnessScript param, but for now
+ // we'll just quietly accept it
+ } else if (scriptPubKey == GetScriptForDestination(p2sh_p2wsh)) {
+ // p2wsh encoded as p2sh; ideally the witness
+ // script was specified in the witnessScript
+ // param, but also support specifying it via
+ // redeemScript param for backwards compat
+ // (in which case ws.IsNull() == true)
+ } else {
+ // otherwise, can't generate scriptPubKey from
+ // either script, so we got unusable parameters
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
+ }
+ } else if (is_p2wsh) {
+ // plain p2wsh; could throw an error if script
+ // was specified by redeemScript rather than
+ // witnessScript (ie, ws.IsNull() == true), but
+ // accept it for backwards compat
+ const CTxDestination p2wsh{WitnessV0ScriptHash(script)};
+ if (scriptPubKey != GetScriptForDestination(p2wsh)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "redeemScript/witnessScript does not match scriptPubKey");
+ }
+ }
}
}
}
+}
+UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType)
+{
int nHashType = ParseSighashString(hashType);
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
@@ -266,6 +309,9 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
+ } else if (serror == SCRIPT_ERR_SIG_NULLFAIL) {
+ // Verification failed (possibly due to insufficient signatures).
+ TxInErrorToJSON(txin, vErrors, "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)");
} else {
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
}
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index c85593e71e..5b92650764 100644
--- a/src/rpc/rawtransaction_util.h
+++ b/src/rpc/rawtransaction_util.h
@@ -12,19 +12,27 @@ class UniValue;
struct CMutableTransaction;
class Coin;
class COutPoint;
+class SigningProvider;
/**
* Sign a transaction with the given keystore and previous transactions
*
* @param mtx The transaction to-be-signed
- * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain
* @param keystore Temporary keystore containing signing keys
- * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call
- * @param tempKeystore Whether to use temporary keystore
+ * @param coins Map of unspent outputs
* @param hashType The signature hash type
* @returns JSON object with details of signed transaction
*/
-UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxs, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins, bool tempKeystore, const UniValue& hashType);
+UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType);
+
+/**
+ * Parse a prevtxs UniValue array and get the map of coins from it
+ *
+ * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain
+ * @param keystore A pointer to the temprorary keystore if there is one
+ * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call
+ */
+void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
/** Create a transaction from univalue parameters */
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf);
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 18f7426bcf..3e5bb85c1c 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -162,7 +162,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
if (jsonRequest.fHelp || jsonRequest.params.size() > 1)
throw std::runtime_error(
RPCHelpMan{"stop",
- "\nStop Bitcoin server.",
+ "\nRequest a graceful shutdown of " PACKAGE_NAME ".",
{},
RPCResults{},
RPCExamples{""},
@@ -173,7 +173,7 @@ UniValue stop(const JSONRPCRequest& jsonRequest)
if (jsonRequest.params[0].isNum()) {
MilliSleep(jsonRequest.params[0].get_int());
}
- return "Bitcoin server stopping";
+ return PACKAGE_NAME " stopping";
}
static UniValue uptime(const JSONRPCRequest& jsonRequest)
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 22d67c34da..adda90c104 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -733,3 +733,21 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
}
return ret;
}
+
+UniValue GetServicesNames(ServiceFlags services)
+{
+ UniValue servicesNames(UniValue::VARR);
+
+ if (services & NODE_NETWORK)
+ servicesNames.push_back("NETWORK");
+ if (services & NODE_GETUTXO)
+ servicesNames.push_back("GETUTXO");
+ if (services & NODE_BLOOM)
+ servicesNames.push_back("BLOOM");
+ if (services & NODE_WITNESS)
+ servicesNames.push_back("WITNESS");
+ if (services & NODE_NETWORK_LIMITED)
+ servicesNames.push_back("NETWORK_LIMITED");
+
+ return servicesNames;
+}
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 4c3322b879..72fc7b6286 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -8,6 +8,7 @@
#include <node/transaction.h>
#include <outputtype.h>
#include <pubkey.h>
+#include <protocol.h>
#include <rpc/protocol.h>
#include <rpc/request.h>
#include <script/script.h>
@@ -90,6 +91,9 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value);
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider);
+/** Returns, given services flags, a list of humanly readable (known) network services */
+UniValue GetServicesNames(ServiceFlags services);
+
struct RPCArg {
enum class Type {
OBJ,
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index f8701b6d01..20fae2eebf 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -334,7 +334,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
opcode == OP_MOD ||
opcode == OP_LSHIFT ||
opcode == OP_RSHIFT)
- return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); // Disabled opcodes.
+ return set_error(serror, SCRIPT_ERR_DISABLED_OPCODE); // Disabled opcodes (CVE-2010-5137).
// With SCRIPT_VERIFY_CONST_SCRIPTCODE, OP_CODESEPARATOR in non-segwit script is rejected even in an unexecuted branch
if (opcode == OP_CODESEPARATOR && sigversion == SigVersion::BASE && (flags & SCRIPT_VERIFY_CONST_SCRIPTCODE))
@@ -1483,6 +1483,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);
}
+ // scriptSig and scriptPubKey must be evaluated sequentially on the same stack
+ // rather than being simply concatenated (see CVE-2010-5141)
std::vector<std::vector<unsigned char> > stack, stackCopy;
if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
// serror is set
diff --git a/src/streams.h b/src/streams.h
index 4e600f1826..517eefc932 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -735,16 +735,17 @@ protected:
size_t nBytes = fread((void*)&vchBuf[pos], 1, readNow, src);
if (nBytes == 0) {
throw std::ios_base::failure(feof(src) ? "CBufferedFile::Fill: end of file" : "CBufferedFile::Fill: fread failed");
- } else {
- nSrcPos += nBytes;
- return true;
}
+ nSrcPos += nBytes;
+ return true;
}
public:
CBufferedFile(FILE *fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nTypeIn, int nVersionIn) :
nType(nTypeIn), nVersion(nVersionIn), nSrcPos(0), nReadPos(0), nReadLimit(std::numeric_limits<uint64_t>::max()), nRewind(nRewindIn), vchBuf(nBufSize, 0)
{
+ if (nRewindIn >= nBufSize)
+ throw std::ios_base::failure("Rewind limit must be less than buffer size");
src = fileIn;
}
@@ -777,8 +778,6 @@ public:
void read(char *pch, size_t nSize) {
if (nSize + nReadPos > nReadLimit)
throw std::ios_base::failure("Read attempted past buffer limit");
- if (nSize + nRewind > vchBuf.size())
- throw std::ios_base::failure("Read larger than buffer size");
while (nSize > 0) {
if (nReadPos == nSrcPos)
Fill();
@@ -802,16 +801,19 @@ public:
//! rewind to a given reading position
bool SetPos(uint64_t nPos) {
- nReadPos = nPos;
- if (nReadPos + nRewind < nSrcPos) {
- nReadPos = nSrcPos - nRewind;
+ size_t bufsize = vchBuf.size();
+ if (nPos + bufsize < nSrcPos) {
+ // rewinding too far, rewind as far as possible
+ nReadPos = nSrcPos - bufsize;
return false;
- } else if (nReadPos > nSrcPos) {
+ }
+ if (nPos > nSrcPos) {
+ // can't go this far forward, go as far as possible
nReadPos = nSrcPos;
return false;
- } else {
- return true;
}
+ nReadPos = nPos;
+ return true;
}
bool Seek(uint64_t nPos) {
diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json
index 9b320b6943..3241f32f56 100644
--- a/src/test/data/script_tests.json
+++ b/src/test/data/script_tests.json
@@ -829,15 +829,16 @@
["NOP", "2SWAP 1", "P2SH,STRICTENC", "INVALID_STACK_OPERATION"],
["1", "2 3 2SWAP 1", "P2SH,STRICTENC", "INVALID_STACK_OPERATION"],
+
+["NOP", "SIZE 1", "P2SH,STRICTENC", "INVALID_STACK_OPERATION"],
+
+["TEST DISABLED OP CODES (CVE-2010-5137)"],
["'a' 'b'", "CAT", "P2SH,STRICTENC", "DISABLED_OPCODE", "CAT disabled"],
["'a' 'b' 0", "IF CAT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "CAT disabled"],
["'abc' 1 1", "SUBSTR", "P2SH,STRICTENC", "DISABLED_OPCODE", "SUBSTR disabled"],
["'abc' 1 1 0", "IF SUBSTR ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "SUBSTR disabled"],
["'abc' 2 0", "IF LEFT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "LEFT disabled"],
["'abc' 2 0", "IF RIGHT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "RIGHT disabled"],
-
-["NOP", "SIZE 1", "P2SH,STRICTENC", "INVALID_STACK_OPERATION"],
-
["'abc'", "IF INVERT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "INVERT disabled"],
["1 2 0 IF AND ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "AND disabled"],
["1 2 0 IF OR ELSE 1 ENDIF", "NOP", "P2SH,STRICTENC", "DISABLED_OPCODE", "OR disabled"],
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index a50d6854f8..b0a613372f 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -151,17 +151,17 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
auto peerLogic = MakeUnique<PeerLogicValidation>(connman.get(), nullptr, scheduler, false);
const Consensus::Params& consensusParams = Params().GetConsensus();
- constexpr int nMaxOutbound = 8;
+ constexpr int max_outbound_full_relay = 8;
CConnman::Options options;
options.nMaxConnections = 125;
- options.nMaxOutbound = nMaxOutbound;
+ options.m_max_outbound_full_relay = max_outbound_full_relay;
options.nMaxFeeler = 1;
connman->Init(options);
std::vector<CNode *> vNodes;
// Mock some outbound peers
- for (int i=0; i<nMaxOutbound; ++i) {
+ for (int i=0; i<max_outbound_full_relay; ++i) {
AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());
}
@@ -190,7 +190,7 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
AddRandomOutboundPeer(vNodes, *peerLogic, connman.get());
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
- for (int i=0; i<nMaxOutbound; ++i) {
+ for (int i=0; i<max_outbound_full_relay; ++i) {
BOOST_CHECK(vNodes[i]->fDisconnect == false);
}
// Last added node should get marked for eviction
@@ -203,10 +203,10 @@ BOOST_AUTO_TEST_CASE(stale_tip_peer_management)
UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
- for (int i=0; i<nMaxOutbound-1; ++i) {
+ for (int i=0; i<max_outbound_full_relay-1; ++i) {
BOOST_CHECK(vNodes[i]->fDisconnect == false);
}
- BOOST_CHECK(vNodes[nMaxOutbound-1]->fDisconnect == true);
+ BOOST_CHECK(vNodes[max_outbound_full_relay-1]->fDisconnect == true);
BOOST_CHECK(vNodes.back()->fDisconnect == false);
bool dummy;
diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp
index 1684258c9f..dc38a1a818 100644
--- a/src/test/merkle_tests.cpp
+++ b/src/test/merkle_tests.cpp
@@ -249,4 +249,104 @@ BOOST_AUTO_TEST_CASE(merkle_test)
}
}
+
+BOOST_AUTO_TEST_CASE(merkle_test_empty_block)
+{
+ bool mutated = false;
+ CBlock block;
+ uint256 root = BlockMerkleRoot(block, &mutated);
+
+ BOOST_CHECK_EQUAL(root.IsNull(), true);
+ BOOST_CHECK_EQUAL(mutated, false);
+}
+
+BOOST_AUTO_TEST_CASE(merkle_test_oneTx_block)
+{
+ bool mutated = false;
+ CBlock block;
+
+ block.vtx.resize(1);
+ CMutableTransaction mtx;
+ mtx.nLockTime = 0;
+ block.vtx[0] = MakeTransactionRef(std::move(mtx));
+ uint256 root = BlockMerkleRoot(block, &mutated);
+ BOOST_CHECK_EQUAL(root, block.vtx[0]->GetHash());
+ BOOST_CHECK_EQUAL(mutated, false);
+}
+
+BOOST_AUTO_TEST_CASE(merkle_test_OddTxWithRepeatedLastTx_block)
+{
+ bool mutated;
+ CBlock block, blockWithRepeatedLastTx;
+
+ block.vtx.resize(3);
+
+ for (std::size_t pos = 0; pos < block.vtx.size(); pos++) {
+ CMutableTransaction mtx;
+ mtx.nLockTime = pos;
+ block.vtx[pos] = MakeTransactionRef(std::move(mtx));
+ }
+
+ blockWithRepeatedLastTx = block;
+ blockWithRepeatedLastTx.vtx.push_back(blockWithRepeatedLastTx.vtx.back());
+
+ uint256 rootofBlock = BlockMerkleRoot(block, &mutated);
+ BOOST_CHECK_EQUAL(mutated, false);
+
+ uint256 rootofBlockWithRepeatedLastTx = BlockMerkleRoot(blockWithRepeatedLastTx, &mutated);
+ BOOST_CHECK_EQUAL(rootofBlock, rootofBlockWithRepeatedLastTx);
+ BOOST_CHECK_EQUAL(mutated, true);
+}
+
+BOOST_AUTO_TEST_CASE(merkle_test_LeftSubtreeRightSubtree)
+{
+ CBlock block, leftSubtreeBlock, rightSubtreeBlock;
+
+ block.vtx.resize(4);
+ std::size_t pos;
+ for (pos = 0; pos < block.vtx.size(); pos++) {
+ CMutableTransaction mtx;
+ mtx.nLockTime = pos;
+ block.vtx[pos] = MakeTransactionRef(std::move(mtx));
+ }
+
+ for (pos = 0; pos < block.vtx.size() / 2; pos++)
+ leftSubtreeBlock.vtx.push_back(block.vtx[pos]);
+
+ for (pos = block.vtx.size() / 2; pos < block.vtx.size(); pos++)
+ rightSubtreeBlock.vtx.push_back(block.vtx[pos]);
+
+ uint256 root = BlockMerkleRoot(block);
+ uint256 rootOfLeftSubtree = BlockMerkleRoot(leftSubtreeBlock);
+ uint256 rootOfRightSubtree = BlockMerkleRoot(rightSubtreeBlock);
+ std::vector<uint256> leftRight;
+ leftRight.push_back(rootOfLeftSubtree);
+ leftRight.push_back(rootOfRightSubtree);
+ uint256 rootOfLR = ComputeMerkleRoot(leftRight);
+
+ BOOST_CHECK_EQUAL(root, rootOfLR);
+}
+
+BOOST_AUTO_TEST_CASE(merkle_test_BlockWitness)
+{
+ CBlock block;
+
+ block.vtx.resize(2);
+ for (std::size_t pos = 0; pos < block.vtx.size(); pos++) {
+ CMutableTransaction mtx;
+ mtx.nLockTime = pos;
+ block.vtx[pos] = MakeTransactionRef(std::move(mtx));
+ }
+
+ uint256 blockWitness = BlockWitnessMerkleRoot(block);
+
+ std::vector<uint256> hashes;
+ hashes.resize(block.vtx.size());
+ hashes[0].SetNull();
+ hashes[1] = block.vtx[1]->GetHash();
+
+ uint256 merkelRootofHashes = ComputeMerkleRoot(hashes);
+
+ BOOST_CHECK_EQUAL(merkelRootofHashes, blockWitness);
+}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp
index b812cef801..638819d564 100644
--- a/src/test/streams_tests.cpp
+++ b/src/test/streams_tests.cpp
@@ -2,6 +2,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <random.h>
#include <streams.h>
#include <test/setup_common.h>
@@ -202,4 +203,247 @@ BOOST_AUTO_TEST_CASE(streams_serializedata_xor)
std::string(ds.begin(), ds.end()));
}
+BOOST_AUTO_TEST_CASE(streams_buffered_file)
+{
+ FILE* file = fsbridge::fopen("streams_test_tmp", "w+b");
+ // The value at each offset is the offset.
+ for (uint8_t j = 0; j < 40; ++j) {
+ fwrite(&j, 1, 1, file);
+ }
+ rewind(file);
+
+ // The buffer size (second arg) must be greater than the rewind
+ // amount (third arg).
+ try {
+ CBufferedFile bfbad(file, 25, 25, 222, 333);
+ BOOST_CHECK(false);
+ } catch (const std::exception& e) {
+ BOOST_CHECK(strstr(e.what(),
+ "Rewind limit must be less than buffer size") != nullptr);
+ }
+
+ // The buffer is 25 bytes, allow rewinding 10 bytes.
+ CBufferedFile bf(file, 25, 10, 222, 333);
+ BOOST_CHECK(!bf.eof());
+
+ // These two members have no functional effect.
+ BOOST_CHECK_EQUAL(bf.GetType(), 222);
+ BOOST_CHECK_EQUAL(bf.GetVersion(), 333);
+
+ uint8_t i;
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 0);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 1);
+
+ // After reading bytes 0 and 1, we're positioned at 2.
+ BOOST_CHECK_EQUAL(bf.GetPos(), 2);
+
+ // Rewind to offset 0, ok (within the 10 byte window).
+ BOOST_CHECK(bf.SetPos(0));
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 0);
+
+ // We can go forward to where we've been, but beyond may fail.
+ BOOST_CHECK(bf.SetPos(2));
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 2);
+
+ // If you know the maximum number of bytes that should be
+ // read to deserialize the variable, you can limit the read
+ // extent. The current file offset is 3, so the following
+ // SetLimit() allows zero bytes to be read.
+ BOOST_CHECK(bf.SetLimit(3));
+ try {
+ bf >> i;
+ BOOST_CHECK(false);
+ } catch (const std::exception& e) {
+ BOOST_CHECK(strstr(e.what(),
+ "Read attempted past buffer limit") != nullptr);
+ }
+ // The default argument removes the limit completely.
+ BOOST_CHECK(bf.SetLimit());
+ // The read position should still be at 3 (no change).
+ BOOST_CHECK_EQUAL(bf.GetPos(), 3);
+
+ // Read from current offset, 3, forward until position 10.
+ for (uint8_t j = 3; j < 10; ++j) {
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, j);
+ }
+ BOOST_CHECK_EQUAL(bf.GetPos(), 10);
+
+ // We're guaranteed (just barely) to be able to rewind to zero.
+ BOOST_CHECK(bf.SetPos(0));
+ BOOST_CHECK_EQUAL(bf.GetPos(), 0);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 0);
+
+ // We can set the position forward again up to the farthest
+ // into the stream we've been, but no farther. (Attempting
+ // to go farther may succeed, but it's not guaranteed.)
+ BOOST_CHECK(bf.SetPos(10));
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 10);
+ BOOST_CHECK_EQUAL(bf.GetPos(), 11);
+
+ // Now it's only guaranteed that we can rewind to offset 1
+ // (current read position, 11, minus rewind amount, 10).
+ BOOST_CHECK(bf.SetPos(1));
+ BOOST_CHECK_EQUAL(bf.GetPos(), 1);
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 1);
+
+ // We can stream into large variables, even larger than
+ // the buffer size.
+ BOOST_CHECK(bf.SetPos(11));
+ {
+ uint8_t a[40 - 11];
+ bf >> a;
+ for (uint8_t j = 0; j < sizeof(a); ++j) {
+ BOOST_CHECK_EQUAL(a[j], 11 + j);
+ }
+ }
+ BOOST_CHECK_EQUAL(bf.GetPos(), 40);
+
+ // We've read the entire file, the next read should throw.
+ try {
+ bf >> i;
+ BOOST_CHECK(false);
+ } catch (const std::exception& e) {
+ BOOST_CHECK(strstr(e.what(),
+ "CBufferedFile::Fill: end of file") != nullptr);
+ }
+ // Attempting to read beyond the end sets the EOF indicator.
+ BOOST_CHECK(bf.eof());
+
+ // Still at offset 40, we can go back 10, to 30.
+ BOOST_CHECK_EQUAL(bf.GetPos(), 40);
+ BOOST_CHECK(bf.SetPos(30));
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, 30);
+ BOOST_CHECK_EQUAL(bf.GetPos(), 31);
+
+ // We're too far to rewind to position zero.
+ BOOST_CHECK(!bf.SetPos(0));
+ // But we should now be positioned at least as far back as allowed
+ // by the rewind window (relative to our farthest read position, 40).
+ BOOST_CHECK(bf.GetPos() <= 30);
+
+ // We can explicitly close the file, or the destructor will do it.
+ bf.fclose();
+
+ fs::remove("streams_test_tmp");
+}
+
+BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
+{
+ // Make this test deterministic.
+ SeedInsecureRand(true);
+
+ for (int rep = 0; rep < 50; ++rep) {
+ FILE* file = fsbridge::fopen("streams_test_tmp", "w+b");
+ size_t fileSize = InsecureRandRange(256);
+ for (uint8_t i = 0; i < fileSize; ++i) {
+ fwrite(&i, 1, 1, file);
+ }
+ rewind(file);
+
+ size_t bufSize = InsecureRandRange(300) + 1;
+ size_t rewindSize = InsecureRandRange(bufSize);
+ CBufferedFile bf(file, bufSize, rewindSize, 222, 333);
+ size_t currentPos = 0;
+ size_t maxPos = 0;
+ for (int step = 0; step < 100; ++step) {
+ if (currentPos >= fileSize)
+ break;
+
+ // We haven't read to the end of the file yet.
+ BOOST_CHECK(!bf.eof());
+ BOOST_CHECK_EQUAL(bf.GetPos(), currentPos);
+
+ // Pretend the file consists of a series of objects of varying
+ // sizes; the boundaries of the objects can interact arbitrarily
+ // with the CBufferFile's internal buffer. These first three
+ // cases simulate objects of various sizes (1, 2, 5 bytes).
+ switch (InsecureRandRange(5)) {
+ case 0: {
+ uint8_t a[1];
+ if (currentPos + 1 > fileSize)
+ continue;
+ bf.SetLimit(currentPos + 1);
+ bf >> a;
+ for (uint8_t i = 0; i < 1; ++i) {
+ BOOST_CHECK_EQUAL(a[i], currentPos);
+ currentPos++;
+ }
+ break;
+ }
+ case 1: {
+ uint8_t a[2];
+ if (currentPos + 2 > fileSize)
+ continue;
+ bf.SetLimit(currentPos + 2);
+ bf >> a;
+ for (uint8_t i = 0; i < 2; ++i) {
+ BOOST_CHECK_EQUAL(a[i], currentPos);
+ currentPos++;
+ }
+ break;
+ }
+ case 2: {
+ uint8_t a[5];
+ if (currentPos + 5 > fileSize)
+ continue;
+ bf.SetLimit(currentPos + 5);
+ bf >> a;
+ for (uint8_t i = 0; i < 5; ++i) {
+ BOOST_CHECK_EQUAL(a[i], currentPos);
+ currentPos++;
+ }
+ break;
+ }
+ case 3: {
+ // Find a byte value (that is at or ahead of the current position).
+ size_t find = currentPos + InsecureRandRange(8);
+ if (find >= fileSize)
+ find = fileSize - 1;
+ bf.FindByte(static_cast<char>(find));
+ // The value at each offset is the offset.
+ BOOST_CHECK_EQUAL(bf.GetPos(), find);
+ currentPos = find;
+
+ bf.SetLimit(currentPos + 1);
+ uint8_t i;
+ bf >> i;
+ BOOST_CHECK_EQUAL(i, currentPos);
+ currentPos++;
+ break;
+ }
+ case 4: {
+ size_t requestPos = InsecureRandRange(maxPos + 4);
+ bool okay = bf.SetPos(requestPos);
+ // The new position may differ from the requested position
+ // because we may not be able to rewind beyond the rewind
+ // window, and we may not be able to move forward beyond the
+ // farthest position we've reached so far.
+ currentPos = bf.GetPos();
+ BOOST_CHECK_EQUAL(okay, currentPos == requestPos);
+ // Check that we can position within the rewind window.
+ if (requestPos <= maxPos &&
+ maxPos > rewindSize &&
+ requestPos >= maxPos - rewindSize) {
+ // We requested a position within the rewind window.
+ BOOST_CHECK(okay);
+ }
+ break;
+ }
+ }
+ if (maxPos < currentPos)
+ maxPos = currentPos;
+ }
+ }
+ fs::remove("streams_test_tmp");
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 65cb956fbe..d0cd4b0a03 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -913,7 +913,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup)
// Results file is formatted like:
//
// <input> || <output>
- BOOST_CHECK_EQUAL(out_sha_hex, "b284f4b4a15dd6bf8c06213a69a004b1960388e1d9917173927db52ac220927f");
+ BOOST_CHECK_EQUAL(out_sha_hex, "94b4ad55c8ac639a56b93e36f7e32e4c611fd7d7dd7b2be6a71707b1eadcaec7");
}
BOOST_AUTO_TEST_CASE(util_FormatMoney)
diff --git a/src/util/system.cpp b/src/util/system.cpp
index c925dec253..8098cde093 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -954,16 +954,18 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
std::string ArgsManager::GetChainName() const
{
LOCK(cs_args);
- bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest");
- bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet");
+ const bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest");
+ const bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet");
+ const bool is_chain_arg_set = IsArgSet("-chain");
- if (fTestNet && fRegTest)
- throw std::runtime_error("Invalid combination of -regtest and -testnet.");
+ if ((int)is_chain_arg_set + (int)fRegTest + (int)fTestNet > 1) {
+ throw std::runtime_error("Invalid combination of -regtest, -testnet and -chain. Can use at most one.");
+ }
if (fRegTest)
return CBaseChainParams::REGTEST;
if (fTestNet)
return CBaseChainParams::TESTNET;
- return CBaseChainParams::MAIN;
+ return GetArg("-chain", CBaseChainParams::MAIN);
}
bool RenameOver(fs::path src, fs::path dest)
diff --git a/src/validation.cpp b/src/validation.cpp
index 3498915192..1faaa411c4 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -428,21 +428,134 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt
return CheckInputs(tx, state, view, flags, cacheSigStore, true, txdata);
}
-/**
- * @param[out] coins_to_uncache Return any outpoints which were not previously present in the
- * coins cache, but were added as a result of validating the tx
- * for mempool acceptance. This allows the caller to optionally
- * remove the cache additions if the associated transaction ends
- * up being rejected by the mempool.
- */
-static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool& pool, CValidationState& state, const CTransactionRef& ptx,
- bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
- bool bypass_limits, const CAmount& nAbsurdFee, std::vector<COutPoint>& coins_to_uncache, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+namespace {
+
+class MemPoolAccept
{
- const CTransaction& tx = *ptx;
- const uint256 hash = tx.GetHash();
- AssertLockHeld(cs_main);
- LOCK(pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
+public:
+ MemPoolAccept(CTxMemPool& mempool) : m_pool(mempool), m_view(&m_dummy), m_viewmempool(&::ChainstateActive().CoinsTip(), m_pool),
+ m_limit_ancestors(gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)),
+ m_limit_ancestor_size(gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000),
+ m_limit_descendants(gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)),
+ m_limit_descendant_size(gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000) {}
+
+ // We put the arguments we're handed into a struct, so we can pass them
+ // around easier.
+ struct ATMPArgs {
+ const CChainParams& m_chainparams;
+ CValidationState &m_state;
+ bool* m_missing_inputs;
+ const int64_t m_accept_time;
+ std::list<CTransactionRef>* m_replaced_transactions;
+ const bool m_bypass_limits;
+ const CAmount& m_absurd_fee;
+ /*
+ * Return any outpoints which were not previously present in the coins
+ * cache, but were added as a result of validating the tx for mempool
+ * acceptance. This allows the caller to optionally remove the cache
+ * additions if the associated transaction ends up being rejected by
+ * the mempool.
+ */
+ std::vector<COutPoint>& m_coins_to_uncache;
+ const bool m_test_accept;
+ };
+
+ // Single transaction acceptance
+ bool AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+private:
+ // All the intermediate state that gets passed between the various levels
+ // of checking a given transaction.
+ struct Workspace {
+ Workspace(const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash()) {}
+ std::set<uint256> m_conflicts;
+ CTxMemPool::setEntries m_all_conflicting;
+ CTxMemPool::setEntries m_ancestors;
+ std::unique_ptr<CTxMemPoolEntry> m_entry;
+
+ bool m_replacement_transaction;
+ CAmount m_modified_fees;
+ CAmount m_conflicting_fees;
+ size_t m_conflicting_size;
+
+ const CTransactionRef& m_ptx;
+ const uint256& m_hash;
+ };
+
+ // Run the policy checks on a given transaction, excluding any script checks.
+ // Looks up inputs, calculates feerate, considers replacement, evaluates
+ // package limits, etc. As this function can be invoked for "free" by a peer,
+ // only tests that are fast should be done here (to avoid CPU DoS).
+ bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
+
+ // Run the script checks using our policy flags. As this can be slow, we should
+ // only invoke this on transactions that have otherwise passed policy checks.
+ bool PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+ // Re-run the script checks, using consensus flags, and try to cache the
+ // result in the scriptcache. This should be done after
+ // PolicyScriptChecks(). This requires that all inputs either be in our
+ // utxo set or in the mempool.
+ bool ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
+ // Try to add the transaction to the mempool, removing any conflicts first.
+ // Returns true if the transaction is in the mempool after any size
+ // limiting is performed, false otherwise.
+ bool Finalize(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
+
+ // Compare a package's feerate against minimum allowed.
+ bool CheckFeeRate(size_t package_size, CAmount package_fee, CValidationState& state)
+ {
+ CAmount mempoolRejectFee = m_pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(package_size);
+ if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee));
+ }
+
+ if (package_fee < ::minRelayTxFee.GetFee(package_size)) {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", package_fee, ::minRelayTxFee.GetFee(package_size)));
+ }
+ return true;
+ }
+
+private:
+ CTxMemPool& m_pool;
+ CCoinsViewCache m_view;
+ CCoinsViewMemPool m_viewmempool;
+ CCoinsView m_dummy;
+
+ // The package limits in effect at the time of invocation.
+ const size_t m_limit_ancestors;
+ const size_t m_limit_ancestor_size;
+ // These may be modified while evaluating a transaction (eg to account for
+ // in-mempool conflicts; see below).
+ size_t m_limit_descendants;
+ size_t m_limit_descendant_size;
+};
+
+bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
+{
+ const CTransactionRef& ptx = ws.m_ptx;
+ const CTransaction& tx = *ws.m_ptx;
+ const uint256& hash = ws.m_hash;
+
+ // Copy/alias what we need out of args
+ CValidationState &state = args.m_state;
+ bool* pfMissingInputs = args.m_missing_inputs;
+ const int64_t nAcceptTime = args.m_accept_time;
+ const bool bypass_limits = args.m_bypass_limits;
+ const CAmount& nAbsurdFee = args.m_absurd_fee;
+ std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
+
+ // Alias what we need out of ws
+ std::set<uint256>& setConflicts = ws.m_conflicts;
+ CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
+ CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
+ std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
+ bool& fReplacementTransaction = ws.m_replacement_transaction;
+ CAmount& nModifiedFees = ws.m_modified_fees;
+ CAmount& nConflictingFees = ws.m_conflicting_fees;
+ size_t& nConflictingSize = ws.m_conflicting_size;
+
if (pfMissingInputs) {
*pfMissingInputs = false;
}
@@ -461,7 +574,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// Do not work on transactions that are too small.
// A transaction with 1 segwit input and 1 P2WPHK output has non-witness size of 82 bytes.
- // Transactions smaller than this are not relayed to reduce unnecessary malloc overhead.
+ // Transactions smaller than this are not relayed to mitigate CVE-2017-12842 by not relaying
+ // 64-byte transactions.
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE)
return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "tx-size-small");
@@ -472,15 +586,14 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-final");
// is it already in the memory pool?
- if (pool.exists(hash)) {
+ if (m_pool.exists(hash)) {
return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-in-mempool");
}
// Check for conflicts with in-memory transactions
- std::set<uint256> setConflicts;
for (const CTxIn &txin : tx.vin)
{
- const CTransaction* ptxConflicting = pool.GetConflictTx(txin.prevout);
+ const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout);
if (ptxConflicting) {
if (!setConflicts.count(ptxConflicting->GetHash()))
{
@@ -514,353 +627,436 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
}
}
- {
- CCoinsView dummy;
- CCoinsViewCache view(&dummy);
-
- LockPoints lp;
- CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
- CCoinsViewMemPool viewMemPool(&coins_cache, pool);
- view.SetBackend(viewMemPool);
-
- // do all inputs exist?
- for (const CTxIn& txin : tx.vin) {
- if (!coins_cache.HaveCoinInCache(txin.prevout)) {
- coins_to_uncache.push_back(txin.prevout);
- }
+ LockPoints lp;
+ m_view.SetBackend(m_viewmempool);
- // Note: this call may add txin.prevout to the coins cache
- // (CoinsTip().cacheCoins) by way of FetchCoin(). It should be removed
- // later (via coins_to_uncache) if this tx turns out to be invalid.
- if (!view.HaveCoin(txin.prevout)) {
- // Are inputs missing because we already have the tx?
- for (size_t out = 0; out < tx.vout.size(); out++) {
- // Optimistically just do efficient check of cache for outputs
- if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) {
- return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known");
- }
- }
- // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
- if (pfMissingInputs) {
- *pfMissingInputs = true;
+ CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
+ // do all inputs exist?
+ for (const CTxIn& txin : tx.vin) {
+ if (!coins_cache.HaveCoinInCache(txin.prevout)) {
+ coins_to_uncache.push_back(txin.prevout);
+ }
+
+ // Note: this call may add txin.prevout to the coins cache
+ // (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed
+ // later (via coins_to_uncache) if this tx turns out to be invalid.
+ if (!m_view.HaveCoin(txin.prevout)) {
+ // Are inputs missing because we already have the tx?
+ for (size_t out = 0; out < tx.vout.size(); out++) {
+ // Optimistically just do efficient check of cache for outputs
+ if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) {
+ return state.Invalid(ValidationInvalidReason::TX_CONFLICT, false, REJECT_DUPLICATE, "txn-already-known");
}
- return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
}
+ // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet
+ if (pfMissingInputs) {
+ *pfMissingInputs = true;
+ }
+ return false; // fMissingInputs and !state.IsInvalid() is used to detect this condition, don't set state.Invalid()
}
+ }
- // Bring the best block into scope
- view.GetBestBlock();
+ // Bring the best block into scope
+ m_view.GetBestBlock();
- // we have all inputs cached now, so switch back to dummy, so we don't need to keep lock on mempool
- view.SetBackend(dummy);
+ // we have all inputs cached now, so switch back to dummy (to protect
+ // against bugs where we pull more inputs from disk that miss being added
+ // to coins_to_uncache)
+ m_view.SetBackend(m_dummy);
- // Only accept BIP68 sequence locked transactions that can be mined in the next
- // block; we don't want our mempool filled up with transactions that can't
- // be mined yet.
- // Must keep pool.cs for this unless we change CheckSequenceLocks to take a
- // CoinsViewCache instead of create its own
- if (!CheckSequenceLocks(pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
- return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final");
+ // Only accept BIP68 sequence locked transactions that can be mined in the next
+ // block; we don't want our mempool filled up with transactions that can't
+ // be mined yet.
+ // Must keep pool.cs for this unless we change CheckSequenceLocks to take a
+ // CoinsViewCache instead of create its own
+ if (!CheckSequenceLocks(m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
+ return state.Invalid(ValidationInvalidReason::TX_PREMATURE_SPEND, false, REJECT_NONSTANDARD, "non-BIP68-final");
- CAmount nFees = 0;
- if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees)) {
- return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
- }
+ CAmount nFees = 0;
+ if (!Consensus::CheckTxInputs(tx, state, m_view, GetSpendHeight(m_view), nFees)) {
+ return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
+ }
- // Check for non-standard pay-to-script-hash in inputs
- if (fRequireStandard && !AreInputsStandard(tx, view))
- return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
+ // Check for non-standard pay-to-script-hash in inputs
+ if (fRequireStandard && !AreInputsStandard(tx, m_view))
+ return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs");
- // Check for non-standard witness in P2WSH
- if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, view))
- return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, REJECT_NONSTANDARD, "bad-witness-nonstandard");
+ // Check for non-standard witness in P2WSH
+ if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view))
+ return state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false, REJECT_NONSTANDARD, "bad-witness-nonstandard");
- int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
+ int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
- // nModifiedFees includes any fee deltas from PrioritiseTransaction
- CAmount nModifiedFees = nFees;
- pool.ApplyDelta(hash, nModifiedFees);
+ // nModifiedFees includes any fee deltas from PrioritiseTransaction
+ nModifiedFees = nFees;
+ m_pool.ApplyDelta(hash, nModifiedFees);
- // Keep track of transactions that spend a coinbase, which we re-scan
- // during reorgs to ensure COINBASE_MATURITY is still met.
- bool fSpendsCoinbase = false;
- for (const CTxIn &txin : tx.vin) {
- const Coin &coin = view.AccessCoin(txin.prevout);
- if (coin.IsCoinBase()) {
- fSpendsCoinbase = true;
- break;
- }
+ // Keep track of transactions that spend a coinbase, which we re-scan
+ // during reorgs to ensure COINBASE_MATURITY is still met.
+ bool fSpendsCoinbase = false;
+ for (const CTxIn &txin : tx.vin) {
+ const Coin &coin = m_view.AccessCoin(txin.prevout);
+ if (coin.IsCoinBase()) {
+ fSpendsCoinbase = true;
+ break;
}
+ }
- CTxMemPoolEntry entry(ptx, nFees, nAcceptTime, ::ChainActive().Height(),
- fSpendsCoinbase, nSigOpsCost, lp);
- unsigned int nSize = entry.GetTxSize();
+ entry.reset(new CTxMemPoolEntry(ptx, nFees, nAcceptTime, ::ChainActive().Height(),
+ fSpendsCoinbase, nSigOpsCost, lp));
+ unsigned int nSize = entry->GetTxSize();
- if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
- return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops",
+ if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
+ return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost));
- CAmount mempoolRejectFee = pool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nSize);
- if (!bypass_limits && mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool min fee not met", strprintf("%d < %d", nModifiedFees, mempoolRejectFee));
- }
-
- // No transactions are allowed below minRelayTxFee except from disconnected blocks
- if (!bypass_limits && nModifiedFees < ::minRelayTxFee.GetFee(nSize)) {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "min relay fee not met", strprintf("%d < %d", nModifiedFees, ::minRelayTxFee.GetFee(nSize)));
- }
+ // No transactions are allowed below minRelayTxFee except from disconnected
+ // blocks
+ if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false;
- if (nAbsurdFee && nFees > nAbsurdFee)
- return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
+ if (nAbsurdFee && nFees > nAbsurdFee)
+ return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false,
REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee));
- // Calculate in-mempool ancestors, up to a limit.
- CTxMemPool::setEntries setAncestors;
- size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
- size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
- size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
- size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
- std::string errString;
- if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
- setAncestors.clear();
- // If CalculateMemPoolAncestors fails second time, we want the original error string.
- std::string dummy_err_string;
- // If the new transaction is relatively small (up to 40k weight)
- // and has at most one ancestor (ie ancestor limit of 2, including
- // the new transaction), allow it if its parent has exactly the
- // descendant limit descendants.
- //
- // This allows protocols which rely on distrusting counterparties
- // being able to broadcast descendants of an unconfirmed transaction
- // to be secure by simply only having two immediately-spendable
- // outputs - one for each counterparty. For more info on the uses for
- // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
- if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
- !pool.CalculateMemPoolAncestors(entry, setAncestors, 2, nLimitAncestorSize, nLimitDescendants + 1, nLimitDescendantSize + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString);
- }
- }
-
- // A transaction that spends outputs that would be replaced by it is invalid. Now
- // that we have the set of all ancestors we can detect this
- // pathological case by making sure setConflicts and setAncestors don't
- // intersect.
- for (CTxMemPool::txiter ancestorIt : setAncestors)
+ const CTxMemPool::setEntries setIterConflicting = m_pool.GetIterSet(setConflicts);
+ // Calculate in-mempool ancestors, up to a limit.
+ if (setConflicts.size() == 1) {
+ // In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
+ // would meet the chain limits after the conflicts have been removed. However, there isn't a practical
+ // way to do this short of calculating the ancestor and descendant sets with an overlay cache of
+ // changed mempool entries. Due to both implementation and runtime complexity concerns, this isn't
+ // very realistic, thus we only ensure a limited set of transactions are RBF'able despite mempool
+ // conflicts here. Importantly, we need to ensure that some transactions which were accepted using
+ // the below carve-out are able to be RBF'ed, without impacting the security the carve-out provides
+ // for off-chain contract systems (see link in the comment below).
+ //
+ // Specifically, the subset of RBF transactions which we allow despite chain limits are those which
+ // conflict directly with exactly one other transaction (but may evict children of said transaction),
+ // and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies"
+ // check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is
+ // amended, we may need to move that check to here instead of removing it wholesale.
+ //
+ // Such transactions are clearly not merging any existing packages, so we are only concerned with
+ // ensuring that (a) no package is growing past the package size (not count) limits and (b) we are
+ // not allowing something to effectively use the (below) carve-out spot when it shouldn't be allowed
+ // to.
+ //
+ // To check these we first check if we meet the RBF criteria, above, and increment the descendant
+ // limits by the direct conflict and its descendants (as these are recalculated in
+ // CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no
+ // removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as
+ // the ancestor limits should be the same for both our new transaction and any conflicts).
+ // We don't bother incrementing m_limit_descendants by the full removal count as that limit never comes
+ // into force here (as we're only adding a single transaction).
+ assert(setIterConflicting.size() == 1);
+ CTxMemPool::txiter conflict = *setIterConflicting.begin();
+
+ m_limit_descendants += 1;
+ m_limit_descendant_size += conflict->GetSizeWithDescendants();
+ }
+
+ std::string errString;
+ if (!m_pool.CalculateMemPoolAncestors(*entry, setAncestors, m_limit_ancestors, m_limit_ancestor_size, m_limit_descendants, m_limit_descendant_size, errString)) {
+ setAncestors.clear();
+ // If CalculateMemPoolAncestors fails second time, we want the original error string.
+ std::string dummy_err_string;
+ // Contracting/payment channels CPFP carve-out:
+ // If the new transaction is relatively small (up to 40k weight)
+ // and has at most one ancestor (ie ancestor limit of 2, including
+ // the new transaction), allow it if its parent has exactly the
+ // descendant limit descendants.
+ //
+ // This allows protocols which rely on distrusting counterparties
+ // being able to broadcast descendants of an unconfirmed transaction
+ // to be secure by simply only having two immediately-spendable
+ // outputs - one for each counterparty. For more info on the uses for
+ // this, see https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
+ if (nSize > EXTRA_DESCENDANT_TX_SIZE_LIMIT ||
+ !m_pool.CalculateMemPoolAncestors(*entry, setAncestors, 2, m_limit_ancestor_size, m_limit_descendants + 1, m_limit_descendant_size + EXTRA_DESCENDANT_TX_SIZE_LIMIT, dummy_err_string)) {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too-long-mempool-chain", errString);
+ }
+ }
+
+ // A transaction that spends outputs that would be replaced by it is invalid. Now
+ // that we have the set of all ancestors we can detect this
+ // pathological case by making sure setConflicts and setAncestors don't
+ // intersect.
+ for (CTxMemPool::txiter ancestorIt : setAncestors)
+ {
+ const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
+ if (setConflicts.count(hashAncestor))
{
- const uint256 &hashAncestor = ancestorIt->GetTx().GetHash();
- if (setConflicts.count(hashAncestor))
- {
- return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-spends-conflicting-tx",
- strprintf("%s spends conflicting transaction %s",
- hash.ToString(),
- hashAncestor.ToString()));
- }
+ return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-txns-spends-conflicting-tx",
+ strprintf("%s spends conflicting transaction %s",
+ hash.ToString(),
+ hashAncestor.ToString()));
}
+ }
- // Check if it's economically rational to mine this transaction rather
- // than the ones it replaces.
- CAmount nConflictingFees = 0;
- size_t nConflictingSize = 0;
- uint64_t nConflictingCount = 0;
- CTxMemPool::setEntries allConflicting;
-
- // If we don't hold the lock allConflicting might be incomplete; the
- // subsequent RemoveStaged() and addUnchecked() calls don't guarantee
- // mempool consistency for us.
- const bool fReplacementTransaction = setConflicts.size();
- if (fReplacementTransaction)
- {
- CFeeRate newFeeRate(nModifiedFees, nSize);
- std::set<uint256> setConflictsParents;
- const int maxDescendantsToVisit = 100;
- const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
- for (const auto& mi : setIterConflicting) {
- // Don't allow the replacement to reduce the feerate of the
- // mempool.
- //
- // We usually don't want to accept replacements with lower
- // feerates than what they replaced as that would lower the
- // feerate of the next block. Requiring that the feerate always
- // be increased is also an easy-to-reason about way to prevent
- // DoS attacks via replacements.
- //
- // We only consider the feerates of transactions being directly
- // replaced, not their indirect descendants. While that does
- // mean high feerate children are ignored when deciding whether
- // or not to replace, we do require the replacement to pay more
- // overall fees too, mitigating most cases.
- CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
- if (newFeeRate <= oldFeeRate)
- {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
- strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
- hash.ToString(),
- newFeeRate.ToString(),
- oldFeeRate.ToString()));
- }
-
- for (const CTxIn &txin : mi->GetTx().vin)
- {
- setConflictsParents.insert(txin.prevout.hash);
- }
+ // Check if it's economically rational to mine this transaction rather
+ // than the ones it replaces.
+ nConflictingFees = 0;
+ nConflictingSize = 0;
+ uint64_t nConflictingCount = 0;
- nConflictingCount += mi->GetCountWithDescendants();
- }
- // This potentially overestimates the number of actual descendants
- // but we just want to be conservative to avoid doing too much
- // work.
- if (nConflictingCount <= maxDescendantsToVisit) {
- // If not too many to replace, then calculate the set of
- // transactions that would have to be evicted
- for (CTxMemPool::txiter it : setIterConflicting) {
- pool.CalculateDescendants(it, allConflicting);
- }
- for (CTxMemPool::txiter it : allConflicting) {
- nConflictingFees += it->GetModifiedFee();
- nConflictingSize += it->GetTxSize();
- }
- } else {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too many potential replacements",
- strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
+ // If we don't hold the lock allConflicting might be incomplete; the
+ // subsequent RemoveStaged() and addUnchecked() calls don't guarantee
+ // mempool consistency for us.
+ fReplacementTransaction = setConflicts.size();
+ if (fReplacementTransaction)
+ {
+ CFeeRate newFeeRate(nModifiedFees, nSize);
+ std::set<uint256> setConflictsParents;
+ const int maxDescendantsToVisit = 100;
+ for (const auto& mi : setIterConflicting) {
+ // Don't allow the replacement to reduce the feerate of the
+ // mempool.
+ //
+ // We usually don't want to accept replacements with lower
+ // feerates than what they replaced as that would lower the
+ // feerate of the next block. Requiring that the feerate always
+ // be increased is also an easy-to-reason about way to prevent
+ // DoS attacks via replacements.
+ //
+ // We only consider the feerates of transactions being directly
+ // replaced, not their indirect descendants. While that does
+ // mean high feerate children are ignored when deciding whether
+ // or not to replace, we do require the replacement to pay more
+ // overall fees too, mitigating most cases.
+ CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize());
+ if (newFeeRate <= oldFeeRate)
+ {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
+ strprintf("rejecting replacement %s; new feerate %s <= old feerate %s",
hash.ToString(),
- nConflictingCount,
- maxDescendantsToVisit));
+ newFeeRate.ToString(),
+ oldFeeRate.ToString()));
}
- for (unsigned int j = 0; j < tx.vin.size(); j++)
+ for (const CTxIn &txin : mi->GetTx().vin)
{
- // We don't want to accept replacements that require low
- // feerate junk to be mined first. Ideally we'd keep track of
- // the ancestor feerates and make the decision based on that,
- // but for now requiring all new inputs to be confirmed works.
- if (!setConflictsParents.count(tx.vin[j].prevout.hash))
- {
- // Rather than check the UTXO set - potentially expensive -
- // it's cheaper to just check if the new input refers to a
- // tx that's in the mempool.
- if (pool.exists(tx.vin[j].prevout.hash)) {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "replacement-adds-unconfirmed",
- strprintf("replacement %s adds unconfirmed input, idx %d",
- hash.ToString(), j));
- }
- }
+ setConflictsParents.insert(txin.prevout.hash);
}
- // The replacement must pay greater fees than the transactions it
- // replaces - if we did the bandwidth used by those conflicting
- // transactions would not be paid for.
- if (nModifiedFees < nConflictingFees)
- {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
- strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
- hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)));
+ nConflictingCount += mi->GetCountWithDescendants();
+ }
+ // This potentially overestimates the number of actual descendants
+ // but we just want to be conservative to avoid doing too much
+ // work.
+ if (nConflictingCount <= maxDescendantsToVisit) {
+ // If not too many to replace, then calculate the set of
+ // transactions that would have to be evicted
+ for (CTxMemPool::txiter it : setIterConflicting) {
+ m_pool.CalculateDescendants(it, allConflicting);
}
-
- // Finally in addition to paying more fees than the conflicts the
- // new transaction must pay for its own bandwidth.
- CAmount nDeltaFees = nModifiedFees - nConflictingFees;
- if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
- {
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
- strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
- hash.ToString(),
- FormatMoney(nDeltaFees),
- FormatMoney(::incrementalRelayFee.GetFee(nSize))));
+ for (CTxMemPool::txiter it : allConflicting) {
+ nConflictingFees += it->GetModifiedFee();
+ nConflictingSize += it->GetTxSize();
}
+ } else {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "too many potential replacements",
+ strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
+ hash.ToString(),
+ nConflictingCount,
+ maxDescendantsToVisit));
}
- constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
-
- // Check against previous transactions
- // The first loop above does all the inexpensive checks.
- // Only if ALL inputs pass do we perform expensive ECDSA signature checks.
- // Helps prevent CPU exhaustion denial-of-service attacks.
- PrecomputedTransactionData txdata(tx);
- if (!CheckInputs(tx, state, view, scriptVerifyFlags, true, false, txdata)) {
- // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
- // need to turn both off, and compare against just turning off CLEANSTACK
- // to see if the failure is specifically due to witness validation.
- CValidationState stateDummy; // Want reported failures to be from first CheckInputs
- if (!tx.HasWitness() && CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
- !CheckInputs(tx, stateDummy, view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
- // Only the witness is missing, so the transaction itself may be fine.
- state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false,
- state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
+ for (unsigned int j = 0; j < tx.vin.size(); j++)
+ {
+ // We don't want to accept replacements that require low
+ // feerate junk to be mined first. Ideally we'd keep track of
+ // the ancestor feerates and make the decision based on that,
+ // but for now requiring all new inputs to be confirmed works.
+ //
+ // Note that if you relax this to make RBF a little more useful,
+ // this may break the CalculateMempoolAncestors RBF relaxation,
+ // above. See the comment above the first CalculateMempoolAncestors
+ // call for more info.
+ if (!setConflictsParents.count(tx.vin[j].prevout.hash))
+ {
+ // Rather than check the UTXO set - potentially expensive -
+ // it's cheaper to just check if the new input refers to a
+ // tx that's in the mempool.
+ if (m_pool.exists(tx.vin[j].prevout.hash)) {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_NONSTANDARD, "replacement-adds-unconfirmed",
+ strprintf("replacement %s adds unconfirmed input, idx %d",
+ hash.ToString(), j));
+ }
}
- assert(IsTransactionReason(state.GetReason()));
- return false; // state filled in by CheckInputs
- }
-
- // Check again against the current block tip's script verification
- // flags to cache our script execution flags. This is, of course,
- // useless if the next block has different script flags from the
- // previous one, but because the cache tracks script flags for us it
- // will auto-invalidate and we'll just have a few blocks of extra
- // misses on soft-fork activation.
- //
- // This is also useful in case of bugs in the standard flags that cause
- // transactions to pass as valid when they're actually invalid. For
- // instance the STRICTENC flag was incorrectly allowing certain
- // CHECKSIG NOT scripts to pass, even though they were invalid.
- //
- // There is a similar check in CreateNewBlock() to prevent creating
- // invalid blocks (using TestBlockValidity), however allowing such
- // transactions into the mempool can be exploited as a DoS attack.
- unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(::ChainActive().Tip(), chainparams.GetConsensus());
- if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, true, txdata)) {
- return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s",
- __func__, hash.ToString(), FormatStateMessage(state));
}
- if (test_accept) {
- // Tx was accepted, but not added
- return true;
+ // The replacement must pay greater fees than the transactions it
+ // replaces - if we did the bandwidth used by those conflicting
+ // transactions would not be paid for.
+ if (nModifiedFees < nConflictingFees)
+ {
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
+ strprintf("rejecting replacement %s, less fees than conflicting txs; %s < %s",
+ hash.ToString(), FormatMoney(nModifiedFees), FormatMoney(nConflictingFees)));
}
- // Remove conflicting transactions from the mempool
- for (CTxMemPool::txiter it : allConflicting)
+ // Finally in addition to paying more fees than the conflicts the
+ // new transaction must pay for its own bandwidth.
+ CAmount nDeltaFees = nModifiedFees - nConflictingFees;
+ if (nDeltaFees < ::incrementalRelayFee.GetFee(nSize))
{
- LogPrint(BCLog::MEMPOOL, "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
- it->GetTx().GetHash().ToString(),
- hash.ToString(),
- FormatMoney(nModifiedFees - nConflictingFees),
- (int)nSize - (int)nConflictingSize);
- if (plTxnReplaced)
- plTxnReplaced->push_back(it->GetSharedTx());
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "insufficient fee",
+ strprintf("rejecting replacement %s, not enough additional fees to relay; %s < %s",
+ hash.ToString(),
+ FormatMoney(nDeltaFees),
+ FormatMoney(::incrementalRelayFee.GetFee(nSize))));
}
- pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
+ }
+ return true;
+}
+
+bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
+{
+ const CTransaction& tx = *ws.m_ptx;
- // This transaction should only count for fee estimation if:
- // - it isn't a BIP 125 replacement transaction (may not be widely supported)
- // - it's not being re-added during a reorg which bypasses typical mempool fee limits
- // - the node is not behind
- // - the transaction is not dependent on any other transactions in the mempool
- bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation() && pool.HasNoInputsOf(tx);
+ CValidationState &state = args.m_state;
- // Store transaction in memory
- pool.addUnchecked(entry, setAncestors, validForFeeEstimation);
+ constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
- // trim mempool and check if tx was trimmed
- if (!bypass_limits) {
- LimitMempoolSize(pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
- if (!pool.exists(hash))
- return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
+ // Check against previous transactions
+ // This is done last to help prevent CPU exhaustion denial-of-service attacks.
+ if (!CheckInputs(tx, state, m_view, scriptVerifyFlags, true, false, txdata)) {
+ // SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
+ // need to turn both off, and compare against just turning off CLEANSTACK
+ // to see if the failure is specifically due to witness validation.
+ CValidationState stateDummy; // Want reported failures to be from first CheckInputs
+ if (!tx.HasWitness() && CheckInputs(tx, stateDummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
+ !CheckInputs(tx, stateDummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
+ // Only the witness is missing, so the transaction itself may be fine.
+ state.Invalid(ValidationInvalidReason::TX_WITNESS_MUTATED, false,
+ state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage());
}
+ assert(IsTransactionReason(state.GetReason()));
+ return false; // state filled in by CheckInputs
}
+ return true;
+}
+
+bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
+{
+ const CTransaction& tx = *ws.m_ptx;
+ const uint256& hash = ws.m_hash;
+
+ CValidationState &state = args.m_state;
+ const CChainParams& chainparams = args.m_chainparams;
+
+ // Check again against the current block tip's script verification
+ // flags to cache our script execution flags. This is, of course,
+ // useless if the next block has different script flags from the
+ // previous one, but because the cache tracks script flags for us it
+ // will auto-invalidate and we'll just have a few blocks of extra
+ // misses on soft-fork activation.
+ //
+ // This is also useful in case of bugs in the standard flags that cause
+ // transactions to pass as valid when they're actually invalid. For
+ // instance the STRICTENC flag was incorrectly allowing certain
+ // CHECKSIG NOT scripts to pass, even though they were invalid.
+ //
+ // There is a similar check in CreateNewBlock() to prevent creating
+ // invalid blocks (using TestBlockValidity), however allowing such
+ // transactions into the mempool can be exploited as a DoS attack.
+ unsigned int currentBlockScriptVerifyFlags = GetBlockScriptFlags(::ChainActive().Tip(), chainparams.GetConsensus());
+ if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, true, txdata)) {
+ return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed against latest-block but not STANDARD flags %s, %s",
+ __func__, hash.ToString(), FormatStateMessage(state));
+ }
+
+ return true;
+}
+
+bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
+{
+ const CTransaction& tx = *ws.m_ptx;
+ const uint256& hash = ws.m_hash;
+ CValidationState &state = args.m_state;
+ const bool bypass_limits = args.m_bypass_limits;
+
+ CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
+ CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
+ const CAmount& nModifiedFees = ws.m_modified_fees;
+ const CAmount& nConflictingFees = ws.m_conflicting_fees;
+ const size_t& nConflictingSize = ws.m_conflicting_size;
+ const bool fReplacementTransaction = ws.m_replacement_transaction;
+ std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
+
+ // Remove conflicting transactions from the mempool
+ for (CTxMemPool::txiter it : allConflicting)
+ {
+ LogPrint(BCLog::MEMPOOL, "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
+ it->GetTx().GetHash().ToString(),
+ hash.ToString(),
+ FormatMoney(nModifiedFees - nConflictingFees),
+ (int)entry->GetTxSize() - (int)nConflictingSize);
+ if (args.m_replaced_transactions)
+ args.m_replaced_transactions->push_back(it->GetSharedTx());
+ }
+ m_pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
+
+ // This transaction should only count for fee estimation if:
+ // - it isn't a BIP 125 replacement transaction (may not be widely supported)
+ // - it's not being re-added during a reorg which bypasses typical mempool fee limits
+ // - the node is not behind
+ // - the transaction is not dependent on any other transactions in the mempool
+ bool validForFeeEstimation = !fReplacementTransaction && !bypass_limits && IsCurrentForFeeEstimation() && m_pool.HasNoInputsOf(tx);
+
+ // Store transaction in memory
+ m_pool.addUnchecked(*entry, setAncestors, validForFeeEstimation);
+
+ // trim mempool and check if tx was trimmed
+ if (!bypass_limits) {
+ LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
+ if (!m_pool.exists(hash))
+ return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
+ }
+ return true;
+}
+
+bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
+{
+ AssertLockHeld(cs_main);
+ LOCK(m_pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
+
+ Workspace workspace(ptx);
+
+ if (!PreChecks(args, workspace)) return false;
+
+ // Only compute the precomputed transaction data if we need to verify
+ // scripts (ie, other policy checks pass). We perform the inexpensive
+ // checks first and avoid hashing and signature verification unless those
+ // checks pass, to mitigate CPU exhaustion denial-of-service attacks.
+ PrecomputedTransactionData txdata(*ptx);
+
+ if (!PolicyScriptChecks(args, workspace, txdata)) return false;
+
+ if (!ConsensusScriptChecks(args, workspace, txdata)) return false;
+
+ // Tx was accepted, but not added
+ if (args.m_test_accept) return true;
+
+ if (!Finalize(args, workspace)) return false;
+
GetMainSignals().TransactionAddedToMempool(ptx);
return true;
}
+} // anon namespace
+
/** (try to) add transaction to memory pool with a specified acceptance time **/
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, CValidationState &state, const CTransactionRef &tx,
bool* pfMissingInputs, int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
bool bypass_limits, const CAmount nAbsurdFee, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
std::vector<COutPoint> coins_to_uncache;
- bool res = AcceptToMemoryPoolWorker(chainparams, pool, state, tx, pfMissingInputs, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept);
+ MemPoolAccept::ATMPArgs args { chainparams, state, pfMissingInputs, nAcceptTime, plTxnReplaced, bypass_limits, nAbsurdFee, coins_to_uncache, test_accept };
+ bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
if (!res) {
// Remove coins that were not present in the coins cache before calling ATMPW;
// this is to prevent memory DoS in case we receive a large number of
@@ -1787,7 +1983,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
// If such overwrites are allowed, coinbases and transactions depending upon those
// can be duplicated to remove the ability to spend the first instance -- even after
// being sent to another address.
- // See BIP30 and http://r6.ca/blog/20120206T005236Z.html for more information.
+ // See BIP30, CVE-2012-1909, and http://r6.ca/blog/20120206T005236Z.html for more information.
// This logic is not necessary for memory pool transactions, as AcceptToMemoryPool
// already refuses previously-known transaction ids entirely.
// This rule was originally applied to all blocks with a timestamp after March 15, 2012, 0:00 UTC.
@@ -2199,14 +2395,12 @@ void static UpdateTip(const CBlockIndex* pindexNew, const CChainParams& chainPar
if (nUpgraded > 0)
AppendWarning(warningMessages, strprintf(_("%d of last 100 blocks have unexpected version").translated, nUpgraded));
}
- LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)", __func__, /* Continued */
+ LogPrintf("%s: new best=%s height=%d version=0x%08x log2_work=%.8g tx=%lu date='%s' progress=%f cache=%.1fMiB(%utxo)%s\n", __func__,
pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion,
log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx,
FormatISO8601DateTime(pindexNew->GetBlockTime()),
- GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize());
- if (!warningMessages.empty())
- LogPrintf(" warning='%s'", warningMessages); /* Continued */
- LogPrintf("\n");
+ GuessVerificationProgress(chainParams.TxData(), pindexNew), ::ChainstateActive().CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), ::ChainstateActive().CoinsTip().GetCacheSize(),
+ !warningMessages.empty() ? strprintf(" warning='%s'", warningMessages) : "");
}
@@ -2469,6 +2663,8 @@ void CChainState::PruneBlockIndexCandidates() {
/**
* Try to make some progress towards making pindexMostWork the active block.
* pblock is either nullptr or a pointer to a CBlock corresponding to pindexMostWork.
+ *
+ * @returns true unless a system error occurred
*/
bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
{
@@ -2588,15 +2784,6 @@ static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
}
}
-/**
- * Make the best chain active, in multiple steps. The result is either failure
- * or an activated best chain. pblock is either nullptr or a pointer to a block
- * that is already loaded (to avoid loading it again from disk).
- *
- * ActivateBestChain is split into steps (see ActivateBestChainStep) so that
- * we avoid holding cs_main for an extended period of time; the length of this
- * call may be quite long during reindexing or a substantial reorg.
- */
bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) {
// Note that while we're often called here from ProcessNewBlock, this is
// far from a guarantee. Things in the P2P/RPC will often end up calling
@@ -2644,8 +2831,10 @@ bool CChainState::ActivateBestChain(CValidationState &state, const CChainParams&
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
- if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace))
+ if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) {
+ // A system error occurred
return false;
+ }
blocks_connected = true;
if (fInvalidFound) {
@@ -3066,6 +3255,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P
return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, "bad-cb-multiple", "more than one coinbase");
// Check transactions
+ // Must check for duplicate inputs (see CVE-2018-17144)
for (const auto& tx : block.vtx)
if (!CheckTransaction(*tx, state, true))
return state.Invalid(state.GetReason(), false, state.GetRejectCode(), state.GetRejectReason(),
@@ -3091,9 +3281,7 @@ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& pa
return (height >= params.SegwitHeight);
}
-// Compute at which vout of the block's coinbase transaction the witness
-// commitment occurs, or -1 if not found.
-static int GetWitnessCommitmentIndex(const CBlock& block)
+int GetWitnessCommitmentIndex(const CBlock& block)
{
int commitpos = -1;
if (!block.vtx.empty()) {
@@ -3399,7 +3587,6 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio
}
}
if (NotifyHeaderTip()) {
- LOCK(cs_main);
if (::ChainstateActive().IsInitialBlockDownload() && ppindex && *ppindex) {
LogPrintf("Synchronizing blockheaders, height: %d (~%.2f%%)\n", (*ppindex)->nHeight, 100.0/((*ppindex)->nHeight+(GetAdjustedTime() - (*ppindex)->GetBlockTime()) / Params().GetConsensus().nPowTargetSpacing) * (*ppindex)->nHeight);
}
@@ -3899,28 +4086,31 @@ bool static LoadBlockIndexDB(const CChainParams& chainparams) EXCLUSIVE_LOCKS_RE
return true;
}
-bool LoadChainTip(const CChainParams& chainparams)
+bool CChainState::LoadChainTip(const CChainParams& chainparams)
{
AssertLockHeld(cs_main);
- const CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
+ const CCoinsViewCache& coins_cache = CoinsTip();
assert(!coins_cache.GetBestBlock().IsNull()); // Never called when the coins view is empty
+ const CBlockIndex* tip = m_chain.Tip();
- if (::ChainActive().Tip() &&
- ::ChainActive().Tip()->GetBlockHash() == coins_cache.GetBestBlock()) return true;
+ if (tip && tip->GetBlockHash() == coins_cache.GetBestBlock()) {
+ return true;
+ }
// Load pointer to end of best chain
CBlockIndex* pindex = LookupBlockIndex(coins_cache.GetBestBlock());
if (!pindex) {
return false;
}
- ::ChainActive().SetTip(pindex);
-
- ::ChainstateActive().PruneBlockIndexCandidates();
+ m_chain.SetTip(pindex);
+ PruneBlockIndexCandidates();
+ tip = m_chain.Tip();
LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n",
- ::ChainActive().Tip()->GetBlockHash().ToString(), ::ChainActive().Height(),
- FormatISO8601DateTime(::ChainActive().Tip()->GetBlockTime()),
- GuessVerificationProgress(chainparams.TxData(), ::ChainActive().Tip()));
+ tip->GetBlockHash().ToString(),
+ m_chain.Height(),
+ FormatISO8601DateTime(tip->GetBlockTime()),
+ GuessVerificationProgress(chainparams.TxData(), tip));
return true;
}
@@ -4055,13 +4245,14 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i
return true;
}
-bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view)
+bool CChainState::ReplayBlocks(const CChainParams& params)
{
LOCK(cs_main);
- CCoinsViewCache cache(view);
+ CCoinsView& db = this->CoinsDB();
+ CCoinsViewCache cache(&db);
- std::vector<uint256> hashHeads = view->GetHeadBlocks();
+ std::vector<uint256> hashHeads = db.GetHeadBlocks();
if (hashHeads.empty()) return true; // We're already in a consistent state.
if (hashHeads.size() != 2) return error("ReplayBlocks(): unknown inconsistent state");
@@ -4121,10 +4312,6 @@ bool CChainState::ReplayBlocks(const CChainParams& params, CCoinsView* view)
return true;
}
-bool ReplayBlocks(const CChainParams& params, CCoinsView* view) {
- return ::ChainstateActive().ReplayBlocks(params, view);
-}
-
//! Helper for CChainState::RewindBlockIndex
void CChainState::EraseBlockData(CBlockIndex* index)
{
diff --git a/src/validation.h b/src/validation.h
index 99850f71d9..96d249b6d3 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -211,7 +211,7 @@ static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024;
* @param[in] pblock The block we want to process.
* @param[in] fForceProcessing Process this block even if unrequested; used for non-network block sources and whitelisted peers.
* @param[out] fNewBlock A boolean which is set to indicate if the block was first received via this call
- * @return True if state.IsValid()
+ * @returns If the block was processed, independently of block validity
*/
bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<const CBlock> pblock, bool fForceProcessing, bool* fNewBlock) LOCKS_EXCLUDED(cs_main);
@@ -240,8 +240,6 @@ bool LoadGenesisBlock(const CChainParams& chainparams);
/** Load the block tree and coins database from disk,
* initializing state if we're running with -reindex. */
bool LoadBlockIndex(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
-/** Update the chain tip based on database information. */
-bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Unload database information */
void UnloadBlockIndex();
/** Run an instance of the script checking thread */
@@ -386,6 +384,9 @@ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& pa
/** When there are blocks in the active chain with missing data, rewind the chainstate and remove them from the block index */
bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
+/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */
+int GetWitnessCommitmentIndex(const CBlock& block);
+
/** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */
void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams);
@@ -400,9 +401,6 @@ public:
bool VerifyDB(const CChainParams& chainparams, CCoinsView *coinsview, int nCheckLevel, int nCheckDepth);
};
-/** Replay blocks that aren't fully applied to the database. */
-bool ReplayBlocks(const CChainParams& params, CCoinsView* view);
-
CBlockIndex* LookupBlockIndex(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Find the last common block between the parameter chain and a locator. */
@@ -653,6 +651,8 @@ public:
*
* If FlushStateMode::NONE is used, then FlushStateToDisk(...) won't do anything
* besides checking if we need to prune.
+ *
+ * @returns true unless a system error occurred
*/
bool FlushStateToDisk(
const CChainParams& chainparams,
@@ -667,7 +667,24 @@ public:
//! if we pruned.
void PruneAndFlush();
- bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main);
+ /**
+ * Make the best chain active, in multiple steps. The result is either failure
+ * or an activated best chain. pblock is either nullptr or a pointer to a block
+ * that is already loaded (to avoid loading it again from disk).
+ *
+ * ActivateBestChain is split into steps (see ActivateBestChainStep) so that
+ * we avoid holding cs_main for an extended period of time; the length of this
+ * call may be quite long during reindexing or a substantial reorg.
+ *
+ * May not be called with cs_main held. May not be called in a
+ * validationinterface callback.
+ *
+ * @returns true unless a system error occurred
+ */
+ bool ActivateBestChain(
+ CValidationState& state,
+ const CChainParams& chainparams,
+ std::shared_ptr<const CBlock> pblock) LOCKS_EXCLUDED(cs_main);
bool AcceptBlock(const std::shared_ptr<const CBlock>& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const FlatFilePos* dbp, bool* fNewBlock) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
@@ -684,7 +701,8 @@ public:
bool InvalidateBlock(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindex) LOCKS_EXCLUDED(cs_main);
void ResetBlockFailureFlags(CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
- bool ReplayBlocks(const CChainParams& params, CCoinsView* view);
+ /** Replay blocks that aren't fully applied to the database. */
+ bool ReplayBlocks(const CChainParams& params);
bool RewindBlockIndex(const CChainParams& params) LOCKS_EXCLUDED(cs_main);
bool LoadGenesisBlock(const CChainParams& chainparams);
@@ -702,6 +720,9 @@ public:
*/
void CheckBlockIndex(const Consensus::Params& consensusParams);
+ /** Update the chain tip based on database information, i.e. CoinsTip()'s best block. */
+ bool LoadChainTip(const CChainParams& chainparams) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+
private:
bool ActivateBestChainStep(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs);
bool ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, ::mempool.cs);
diff --git a/src/wallet/load.h b/src/wallet/load.h
index 81f078fd10..5a62e29303 100644
--- a/src/wallet/load.h
+++ b/src/wallet/load.h
@@ -17,7 +17,7 @@ class Chain;
//! Responsible for reading and validating the -wallet arguments and verifying the wallet database.
//! This function will perform salvage on the wallet if requested, as long as only one wallet is
-//! being loaded (WalletParameterInteraction forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet).
+//! being loaded (WalletInit::ParameterInteraction() forbids -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet).
bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wallet_files);
//! Load wallet databases.
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 22a5f7e249..216205ed61 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1648,8 +1648,10 @@ static UniValue gettransaction(const JSONRPCRequest& request)
"\nGet detailed information about in-wallet transaction <txid>\n",
{
{"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
- {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Whether to include watch-only addresses in balance calculation and details[]"},
- {"decode", RPCArg::Type::BOOL, /* default */ "false", "Whether to add a field with the decoded transaction"},
+ {"include_watchonly", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false",
+ "Whether to include watch-only addresses in balance calculation and details[]"},
+ {"verbose", RPCArg::Type::BOOL, /* default */ "false",
+ "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
},
RPCResult{
"{\n"
@@ -1685,7 +1687,8 @@ static UniValue gettransaction(const JSONRPCRequest& request)
" ,...\n"
" ],\n"
" \"hex\" : \"data\" (string) Raw data for transaction\n"
- " \"decoded\" : transaction (json object) Optional, the decoded transaction\n"
+ " \"decoded\" : transaction (json object) Optional, the decoded transaction (only present when `verbose` is passed), equivalent to the\n"
+ " RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed.\n"
"}\n"
},
RPCExamples{
@@ -1711,7 +1714,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
filter |= ISMINE_WATCH_ONLY;
}
- bool decode_tx = request.params[2].isNull() ? false : request.params[2].get_bool();
+ bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool();
UniValue entry(UniValue::VOBJ);
auto it = pwallet->mapWallet.find(hash);
@@ -1738,7 +1741,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags());
entry.pushKV("hex", strHex);
- if (decode_tx) {
+ if (verbose) {
UniValue decoded(UniValue::VOBJ);
TxToUniv(*wtx.tx, uint256(), decoded, false);
entry.pushKV("decoded", decoded);
@@ -3280,7 +3283,10 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
}
pwallet->chain().findCoins(coins);
- return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]);
+ // Parse the prevtxs array
+ ParsePrevouts(request.params[1], nullptr, coins);
+
+ return SignTransaction(mtx, pwallet, coins, request.params[2]);
}
static UniValue bumpfee(const JSONRPCRequest& request)
@@ -4186,7 +4192,7 @@ static const CRPCCommand commands[] =
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
- { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","decode"} },
+ { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","verbose"} },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
{ "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} },
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 7629a40c5e..7cf09d554b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -4254,7 +4254,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, uint64_t wallet_creation_flags)
{
- const std::string& walletFile = WalletDataFilePath(location.GetPath()).string();
+ const std::string walletFile = WalletDataFilePath(location.GetPath()).string();
// needed to restore wallet transaction meta data after -zapwallettxes
std::vector<CWalletTx> vWtx;
diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h
index 22aca65990..2e1fdf4f3a 100644
--- a/src/walletinitinterface.h
+++ b/src/walletinitinterface.h
@@ -5,10 +5,6 @@
#ifndef BITCOIN_WALLETINITINTERFACE_H
#define BITCOIN_WALLETINITINTERFACE_H
-#include <string>
-
-class CScheduler;
-class CRPCTable;
struct InitInterfaces;
class WalletInitInterface {