aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/Makefile.qt.include4
-rw-r--r--src/bech32.cpp8
-rw-r--r--src/bech32.h2
-rw-r--r--src/chain.h6
-rw-r--r--src/dummywallet.cpp7
-rw-r--r--src/init.cpp31
-rw-r--r--src/interfaces/node.cpp9
-rw-r--r--src/interfaces/node.h5
-rw-r--r--src/interfaces/wallet.cpp2
-rw-r--r--src/net.cpp74
-rw-r--r--src/net.h107
-rw-r--r--src/net_processing.cpp331
-rw-r--r--src/node/coinstats.cpp77
-rw-r--r--src/node/coinstats.h33
-rw-r--r--src/qt/addresstablemodel.cpp10
-rw-r--r--src/qt/askpassphrasedialog.cpp48
-rw-r--r--src/qt/askpassphrasedialog.h5
-rw-r--r--src/qt/bantablemodel.cpp4
-rw-r--r--src/qt/bitcoinamountfield.cpp3
-rw-r--r--src/qt/bitcoingui.cpp57
-rw-r--r--src/qt/bitcoingui.h1
-rw-r--r--src/qt/bitcoinstrings.cpp3
-rw-r--r--src/qt/createwalletdialog.cpp61
-rw-r--r--src/qt/createwalletdialog.h35
-rw-r--r--src/qt/forms/createwalletdialog.ui151
-rw-r--r--src/qt/forms/debugwindow.ui19
-rw-r--r--src/qt/guiconstants.h2
-rw-r--r--src/qt/guiutil.cpp22
-rw-r--r--src/qt/guiutil.h8
-rw-r--r--src/qt/locale/bitcoin_en.ts134
-rw-r--r--src/qt/macdockiconhandler.mm21
-rw-r--r--src/qt/overviewpage.cpp5
-rw-r--r--src/qt/paymentserver.cpp6
-rw-r--r--src/qt/peertablemodel.cpp4
-rw-r--r--src/qt/platformstyle.cpp5
-rw-r--r--src/qt/platformstyle.h3
-rw-r--r--src/qt/receivecoinsdialog.cpp2
-rw-r--r--src/qt/recentrequeststablemodel.cpp4
-rw-r--r--src/qt/recentrequeststablemodel.h2
-rw-r--r--src/qt/rpcconsole.cpp39
-rw-r--r--src/qt/rpcconsole.h3
-rw-r--r--src/qt/sendcoinsdialog.cpp9
-rw-r--r--src/qt/splashscreen.cpp16
-rw-r--r--src/qt/transactiontablemodel.cpp6
-rw-r--r--src/qt/transactionview.cpp12
-rw-r--r--src/qt/transactionview.h2
-rw-r--r--src/qt/walletcontroller.cpp176
-rw-r--r--src/qt/walletcontroller.h79
-rw-r--r--src/qt/walletview.cpp4
-rw-r--r--src/rpc/blockchain.cpp105
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/rpc/rawtransaction.cpp5
-rw-r--r--src/rpc/rawtransaction_util.cpp8
-rw-r--r--src/rpc/rawtransaction_util.h14
-rw-r--r--src/rpc/util.cpp9
-rw-r--r--src/serialize.h13
-rw-r--r--src/test/README.md4
-rw-r--r--src/test/denialofservice_tests.cpp12
-rw-r--r--src/test/descriptor_tests.cpp68
-rw-r--r--src/test/serialize_tests.cpp8
-rw-r--r--src/test/txvalidationcache_tests.cpp20
-rw-r--r--src/test/util_tests.cpp18
-rw-r--r--src/util/error.cpp8
-rw-r--r--src/util/error.h6
-rw-r--r--src/util/string.cpp5
-rw-r--r--src/util/string.h35
-rw-r--r--src/validation.cpp219
-rw-r--r--src/wallet/load.h2
-rw-r--r--src/wallet/rpcdump.cpp3
-rw-r--r--src/wallet/rpcwallet.cpp24
-rw-r--r--src/wallet/test/wallet_tests.cpp22
-rw-r--r--src/wallet/wallet.cpp170
-rw-r--r--src/wallet/wallet.h98
-rw-r--r--src/walletinitinterface.h4
75 files changed, 1712 insertions, 830 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 141d8e68ea..8fc7f61d4b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -156,6 +156,7 @@ BITCOIN_CORE_H = \
netbase.h \
netmessagemaker.h \
node/coin.h \
+ node/coinstats.h \
node/psbt.h \
node/transaction.h \
noui.h \
@@ -211,6 +212,7 @@ BITCOIN_CORE_H = \
util/memory.h \
util/moneystr.h \
util/rbf.h \
+ util/string.h \
util/threadnames.h \
util/time.h \
util/translation.h \
@@ -277,6 +279,7 @@ libbitcoin_server_a_SOURCES = \
net.cpp \
net_processing.cpp \
node/coin.cpp \
+ node/coinstats.cpp \
node/psbt.cpp \
node/transaction.cpp \
noui.cpp \
@@ -501,6 +504,7 @@ libbitcoin_util_a_SOURCES = \
util/rbf.cpp \
util/threadnames.cpp \
util/strencodings.cpp \
+ util/string.cpp \
util/time.cpp \
util/url.cpp \
util/validation.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/bech32.cpp b/src/bech32.cpp
index d6b29391a9..4c966350b4 100644
--- a/src/bech32.cpp
+++ b/src/bech32.cpp
@@ -4,6 +4,8 @@
#include <bech32.h>
+#include <assert.h>
+
namespace
{
@@ -58,7 +60,7 @@ uint32_t PolyMod(const data& v)
// During the course of the loop below, `c` contains the bitpacked coefficients of the
// polynomial constructed from just the values of v that were processed so far, mod g(x). In
- // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of
+ // the above example, `c` initially corresponds to 1 mod g(x), and after processing 2 inputs of
// v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value
// for `c`.
uint32_t c = 1;
@@ -145,6 +147,10 @@ namespace bech32
/** Encode a Bech32 string. */
std::string Encode(const std::string& hrp, const data& values) {
+ // First ensure that the HRP is all lowercase. BIP-173 requires an encoder
+ // to return a lowercase Bech32 string, but if given an uppercase HRP, the
+ // result will always be invalid.
+ for (const char& c : hrp) assert(c < 'A' || c > 'Z');
data checksum = CreateChecksum(hrp, values);
data combined = Cat(values, checksum);
std::string ret = hrp + '1';
diff --git a/src/bech32.h b/src/bech32.h
index 2e2823e974..fb39cd352b 100644
--- a/src/bech32.h
+++ b/src/bech32.h
@@ -19,7 +19,7 @@
namespace bech32
{
-/** Encode a Bech32 string. Returns the empty string in case of failure. */
+/** Encode a Bech32 string. If hrp contains uppercase characters, this will cause an assertion error. */
std::string Encode(const std::string& hrp, const std::vector<uint8_t>& values);
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
diff --git a/src/chain.h b/src/chain.h
index dd9cc2a598..1b67ebbe41 100644
--- a/src/chain.h
+++ b/src/chain.h
@@ -95,8 +95,8 @@ enum BlockStatus: uint32_t {
//! Unused.
BLOCK_VALID_UNKNOWN = 0,
- //! Parsed, version ok, hash satisfies claimed PoW, 1 <= vtx count <= max, timestamp not in future
- BLOCK_VALID_HEADER = 1,
+ //! Reserved (was BLOCK_VALID_HEADER).
+ BLOCK_VALID_RESERVED = 1,
//! All parent headers found, difficulty matches, timestamp >= median previous, checkpoint. Implies all parents
//! are also at least TREE.
@@ -117,7 +117,7 @@ enum BlockStatus: uint32_t {
BLOCK_VALID_SCRIPTS = 5,
//! All validity bits.
- BLOCK_VALID_MASK = BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS |
+ BLOCK_VALID_MASK = BLOCK_VALID_RESERVED | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS |
BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS,
BLOCK_HAVE_DATA = 8, //!< full block available in blk*.dat
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 dd43ef71c9..bb82130542 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -438,9 +438,17 @@ void SetupServerArgs()
#else
hidden_args.emplace_back("-upnp");
#endif
- gArgs.AddArg("-whitebind=<addr>", "Bind to given address and whitelist peers connecting to it. Use [host]:port notation for IPv6", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- gArgs.AddArg("-whitelist=<IP address or network>", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or CIDR notated network (e.g. 1.2.3.0/24). Can be specified multiple times."
- " Whitelisted peers cannot be DoS banned", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ gArgs.AddArg("-whitebind=<[permissions@]addr>", "Bind to given address and whitelist peers connecting to it. "
+ "Use [host]:port notation for IPv6. Allowed permissions are bloomfilter (allow requesting BIP37 filtered blocks and transactions), "
+ "noban (do not ban for misbehavior), "
+ "forcerelay (relay even non-standard transactions), "
+ "relay (relay even in -blocksonly mode), "
+ "and mempool (allow requesting BIP35 mempool contents). "
+ "Specify multiple permissions separated by commas (default: noban,mempool,relay). Can be specified multiple times.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+
+ gArgs.AddArg("-whitelist=<[permissions@]IP address or network>", "Whitelist peers connecting from the given IP address (e.g. 1.2.3.4) or "
+ "CIDR notated network(e.g. 1.2.3.0/24). Uses same permissions as "
+ "-whitebind. Can be specified multiple times." , ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
g_wallet_init_interface.AddWalletOptions();
@@ -510,8 +518,8 @@ void SetupServerArgs()
gArgs.AddArg("-datacarriersize", strprintf("Maximum size of data in data carrier transactions we relay and mine (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
- gArgs.AddArg("-whitelistforcerelay", strprintf("Force relay of transactions from whitelisted inbound peers even if the transactions were already in the mempool or violate local relay policy (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
- gArgs.AddArg("-whitelistrelay", strprintf("Accept relayed transactions received from whitelisted inbound peers even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
+ gArgs.AddArg("-whitelistforcerelay", strprintf("Add 'forcerelay' permission to whitelisted inbound peers with default permissions. This will relay transactions even if the transactions were already in the mempool or violate local relay policy. (default: %d)", DEFAULT_WHITELISTFORCERELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
+ gArgs.AddArg("-whitelistrelay", strprintf("Add 'relay' permission to whitelisted inbound peers with default permissions. The will accept relayed transactions even when not relaying transactions (default: %d)", DEFAULT_WHITELISTRELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg("-blockmaxweight=<n>", strprintf("Set maximum BIP141 block weight (default: %d)", DEFAULT_BLOCK_MAX_WEIGHT), ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
@@ -1052,7 +1060,7 @@ bool AppInitParameterInteraction()
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-incrementalrelayfee", ""), n))
- return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")));
+ return InitError(AmountErrMsg("incrementalrelayfee", gArgs.GetArg("-incrementalrelayfee", "")).translated);
incrementalRelayFee = CFeeRate(n);
}
@@ -1096,9 +1104,9 @@ bool AppInitParameterInteraction()
if (gArgs.IsArgSet("-minrelaytxfee")) {
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n)) {
- return InitError(AmountErrMsg("minrelaytxfee", gArgs.GetArg("-minrelaytxfee", "")));
+ 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
@@ -1112,7 +1120,7 @@ bool AppInitParameterInteraction()
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n))
- return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")));
+ return InitError(AmountErrMsg("blockmintxfee", gArgs.GetArg("-blockmintxfee", "")).translated);
}
// Feerate used to define dust. Shouldn't be changed lightly as old
@@ -1121,7 +1129,7 @@ bool AppInitParameterInteraction()
{
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n))
- return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")));
+ return InitError(AmountErrMsg("dustrelayfee", gArgs.GetArg("-dustrelayfee", "")).translated);
dustRelayFee = CFeeRate(n);
}
@@ -1752,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..ccafc3ac8c 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 {
@@ -258,6 +260,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..e8c3d0b721 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;
@@ -200,6 +202,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/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index 077dc1ab4d..0c8d92eba5 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -65,7 +65,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co
WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx)
{
WalletTxStatus result;
- result.block_height = locked_chain.getBlockHeight(wtx.hashBlock).get_value_or(std::numeric_limits<int>::max());
+ result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max());
result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain);
result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain);
result.time_received = wtx.nTimeReceived;
diff --git a/src/net.cpp b/src/net.cpp
index 337d1f6a46..89f82aa3d2 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -352,7 +352,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 +442,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 +499,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 +530,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 +822,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 +905,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)) {
@@ -1655,7 +1665,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 +1725,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 +1738,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 +1761,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 +1835,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 +1929,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 +1948,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 +2251,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 +2329,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 +2639,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 +2658,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..e9ea0162c9 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();
@@ -355,7 +360,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);
@@ -414,9 +419,17 @@ private:
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 +455,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 +694,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 +714,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 +775,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;
@@ -847,20 +871,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..7f2fea5584 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:
@@ -425,7 +425,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 +757,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 +1330,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 +1449,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 +1513,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 +1538,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 +1778,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 +2003,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 +2024,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 +2096,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 +2140,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 +2226,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 +2267,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 +2486,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;
}
@@ -2990,6 +3007,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 +3052,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 +3146,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 +3165,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 +3181,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 +3196,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 +3475,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 +3604,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 +3612,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 +3820,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 +4097,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/node/coinstats.cpp b/src/node/coinstats.cpp
new file mode 100644
index 0000000000..e1891b9898
--- /dev/null
+++ b/src/node/coinstats.cpp
@@ -0,0 +1,77 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <node/coinstats.h>
+
+#include <amount.h>
+#include <coins.h>
+#include <chain.h>
+#include <hash.h>
+#include <serialize.h>
+#include <validation.h>
+#include <uint256.h>
+#include <util/system.h>
+
+#include <map>
+
+#include <boost/thread.hpp>
+
+
+static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
+{
+ assert(!outputs.empty());
+ ss << hash;
+ ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
+ stats.nTransactions++;
+ for (const auto& output : outputs) {
+ ss << VARINT(output.first + 1);
+ ss << output.second.out.scriptPubKey;
+ ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
+ stats.nTransactionOutputs++;
+ stats.nTotalAmount += output.second.out.nValue;
+ stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
+ 2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
+ }
+ ss << VARINT(0u);
+}
+
+//! Calculate statistics about the unspent transaction output set
+bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
+{
+ std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
+ assert(pcursor);
+
+ CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
+ stats.hashBlock = pcursor->GetBestBlock();
+ {
+ LOCK(cs_main);
+ stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
+ }
+ ss << stats.hashBlock;
+ uint256 prevkey;
+ std::map<uint32_t, Coin> outputs;
+ while (pcursor->Valid()) {
+ boost::this_thread::interruption_point();
+ COutPoint key;
+ Coin coin;
+ if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
+ if (!outputs.empty() && key.hash != prevkey) {
+ ApplyStats(stats, ss, prevkey, outputs);
+ outputs.clear();
+ }
+ prevkey = key.hash;
+ outputs[key.n] = std::move(coin);
+ } else {
+ return error("%s: unable to read value", __func__);
+ }
+ pcursor->Next();
+ }
+ if (!outputs.empty()) {
+ ApplyStats(stats, ss, prevkey, outputs);
+ }
+ stats.hashSerialized = ss.GetHash();
+ stats.nDiskSize = view->EstimateSize();
+ return true;
+}
diff --git a/src/node/coinstats.h b/src/node/coinstats.h
new file mode 100644
index 0000000000..7c11aab8bd
--- /dev/null
+++ b/src/node/coinstats.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_NODE_COINSTATS_H
+#define BITCOIN_NODE_COINSTATS_H
+
+#include <amount.h>
+#include <uint256.h>
+
+#include <cstdint>
+
+class CCoinsView;
+
+struct CCoinsStats
+{
+ int nHeight;
+ uint256 hashBlock;
+ uint64_t nTransactions;
+ uint64_t nTransactionOutputs;
+ uint64_t nBogoSize;
+ uint256 hashSerialized;
+ uint64_t nDiskSize;
+ CAmount nTotalAmount;
+
+ CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {}
+};
+
+//! Calculate statistics about the unspent transaction output set
+bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats);
+
+#endif // BITCOIN_NODE_COINSTATS_H
diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp
index 29423db3d0..131cceccbe 100644
--- a/src/qt/addresstablemodel.cpp
+++ b/src/qt/addresstablemodel.cpp
@@ -10,6 +10,8 @@
#include <key_io.h>
#include <wallet/wallet.h>
+#include <algorithm>
+
#include <QFont>
#include <QDebug>
@@ -86,18 +88,18 @@ public:
QString::fromStdString(EncodeDestination(address.dest))));
}
}
- // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
+ // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order
// Even though the map is already sorted this re-sorting step is needed because the originating map
// is sorted by binary address, not by base58() address.
- qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
+ std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
}
void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
{
// Find address / label in model
- QList<AddressTableEntry>::iterator lower = qLowerBound(
+ QList<AddressTableEntry>::iterator lower = std::lower_bound(
cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
- QList<AddressTableEntry>::iterator upper = qUpperBound(
+ QList<AddressTableEntry>::iterator upper = std::upper_bound(
cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
int lowerIndex = (lower - cachedAddressTable.begin());
int upperIndex = (upper - cachedAddressTable.begin());
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp
index a89a15bc9d..c9f17d12ec 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);
@@ -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/bantablemodel.cpp b/src/qt/bantablemodel.cpp
index 8a6b205cd8..efc726e09e 100644
--- a/src/qt/bantablemodel.cpp
+++ b/src/qt/bantablemodel.cpp
@@ -10,6 +10,8 @@
#include <sync.h>
#include <util/time.h>
+#include <algorithm>
+
#include <QDebug>
#include <QList>
@@ -61,7 +63,7 @@ public:
if (sortColumn >= 0)
// sort cachedBanlist (use stable sort to prevent rows jumping around unnecessarily)
- qStableSort(cachedBanlist.begin(), cachedBanlist.end(), BannedNodeLessThan(sortColumn, sortOrder));
+ std::stable_sort(cachedBanlist.begin(), cachedBanlist.end(), BannedNodeLessThan(sortColumn, sortOrder));
}
int size() const
diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp
index 5854ade655..9fa49b87fa 100644
--- a/src/qt/bitcoinamountfield.cpp
+++ b/src/qt/bitcoinamountfield.cpp
@@ -6,6 +6,7 @@
#include <qt/bitcoinunits.h>
#include <qt/guiconstants.h>
+#include <qt/guiutil.h>
#include <qt/qvaluecombobox.h>
#include <QApplication>
@@ -121,7 +122,7 @@ public:
const QFontMetrics fm(fontMetrics());
int h = lineEdit()->minimumSizeHint().height();
- int w = fm.width(BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
+ int w = GUIUtil::TextWidth(fm, BitcoinUnits::format(BitcoinUnits::BTC, BitcoinUnits::maxMoney(), false, BitcoinUnits::separatorAlways));
w += 2; // cursor blinking space
QStyleOptionSpinBox opt;
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index bc9af9793e..c672171cfb 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>
@@ -40,7 +41,6 @@
#include <QApplication>
#include <QComboBox>
#include <QDateTime>
-#include <QDesktopWidget>
#include <QDragEnterEvent>
#include <QListWidget>
#include <QMenu>
@@ -48,6 +48,7 @@
#include <QMessageBox>
#include <QMimeData>
#include <QProgressDialog>
+#include <QScreen>
#include <QSettings>
#include <QShortcut>
#include <QStackedWidget>
@@ -81,7 +82,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty
QSettings settings;
if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) {
// Restore failed (perhaps missing setting), center the window
- move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center());
+ move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
}
#ifdef ENABLE_WALLET
@@ -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,8 @@ 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 +385,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 +400,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 +427,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 +473,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);
@@ -1407,7 +1392,7 @@ UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *pl
const QFontMetrics fm(font());
for (const BitcoinUnits::Unit unit : units)
{
- max_width = qMax(max_width, fm.width(BitcoinUnits::longName(unit)));
+ max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit)));
}
setMinimumSize(max_width, 0);
setAlignment(Qt::AlignRight | Qt::AlignVCenter);
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 87736cd185..5cde21eec6 100644
--- a/src/qt/bitcoinstrings.cpp
+++ b/src/qt/bitcoinstrings.cpp
@@ -131,12 +131,12 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Initialization sanity check failed. %s is shu
QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -onion address or hostname: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address or hostname: '%s'"),
+QT_TRANSLATE_NOOP("bitcoin-core", "Invalid P2P permission: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -%s=<amount>: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -discardfee=<amount>: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -fallbackfee=<amount>: '%s'"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"),
QT_TRANSLATE_NOOP("bitcoin-core", "Invalid netmask specified in -whitelist: '%s'"),
-QT_TRANSLATE_NOOP("bitcoin-core", "Keypool ran out, please call keypoolrefill first"),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading P2P addresses..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading banlist..."),
QT_TRANSLATE_NOOP("bitcoin-core", "Loading block index..."),
@@ -170,7 +170,6 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Transaction amounts must not be negative"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction fee and change calculation failed"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction has too long of a mempool chain"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction must have at least one recipient"),
-QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large for fee policy"),
QT_TRANSLATE_NOOP("bitcoin-core", "Transaction too large"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer (bind returned error %s)"),
QT_TRANSLATE_NOOP("bitcoin-core", "Unable to bind to %s on this computer. %s is probably already running."),
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
new file mode 100644
index 0000000000..10262c37c3
--- /dev/null
+++ b/src/qt/createwalletdialog.cpp
@@ -0,0 +1,61 @@
+// 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 disable_privkeys_checkbox when encrypt is set to true, enable it when encrypt 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::encrypt() const
+{
+ return ui->encrypt_wallet_checkbox->isChecked();
+}
+
+bool CreateWalletDialog::disablePrivateKeys() const
+{
+ return ui->disable_privkeys_checkbox->isChecked();
+}
+
+bool CreateWalletDialog::blank() 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..a1365b5969
--- /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 encrypt() const;
+ bool disablePrivateKeys() const;
+ bool blank() const;
+
+private:
+ Ui::CreateWalletDialog *ui;
+};
+
+#endif // BITCOIN_QT_CREATEWALLETDIALOG_H
diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui
new file mode 100644
index 0000000000..1fbaeeaaab
--- /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 password 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/debugwindow.ui b/src/qt/forms/debugwindow.ui
index 6e52c5e477..be807b20c0 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -15,6 +15,25 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
+ <widget class="QLabel" name="label_alerts">
+ <property name="visible">
+ <bool>false</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; }</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="margin">
+ <number>3</number>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
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 dc1da7f8a9..070df31aa6 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -39,7 +39,6 @@
#include <QClipboard>
#include <QDateTime>
#include <QDesktopServices>
-#include <QDesktopWidget>
#include <QDoubleValidator>
#include <QFileDialog>
#include <QFont>
@@ -58,9 +57,10 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#include <objc/objc-runtime.h>
#include <CoreServices/CoreServices.h>
#include <QProcess>
+
+void ForceActivation();
#endif
namespace GUIUtil {
@@ -360,10 +360,7 @@ bool isObscured(QWidget *w)
void bringToFront(QWidget* w)
{
#ifdef Q_OS_MAC
- // Force application activation on macOS. With Qt 5.4 this is required when
- // an action in the dock menu is triggered.
- id app = objc_msgSend((id) objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
- objc_msgSend(app, sel_registerName("activateIgnoringOtherApps:"), YES);
+ ForceActivation();
#endif
if (w) {
@@ -916,7 +913,7 @@ qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal m
while(font_size >= minPointSize) {
font.setPointSizeF(font_size);
QFontMetrics fm(font);
- if (fm.width(text) < width) {
+ if (TextWidth(fm, text) < width) {
break;
}
font_size -= 0.5;
@@ -948,7 +945,7 @@ void PolishProgressDialog(QProgressDialog* dialog)
{
#ifdef Q_OS_MAC
// Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357.
- const int margin = dialog->fontMetrics().width("X");
+ const int margin = TextWidth(dialog->fontMetrics(), ("X"));
dialog->resize(dialog->width() + 2 * margin, dialog->height());
dialog->show();
#else
@@ -956,4 +953,13 @@ void PolishProgressDialog(QProgressDialog* dialog)
#endif
}
+int TextWidth(const QFontMetrics& fm, const QString& text)
+{
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
+ return fm.horizontalAdvance(text);
+#else
+ return fm.width(text);
+#endif
+}
+
} // namespace GUIUtil
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index bea4a83494..9db92f94d7 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -257,6 +257,14 @@ namespace GUIUtil
// Fix known bugs in QProgressDialog class.
void PolishProgressDialog(QProgressDialog* dialog);
+
+ /**
+ * Returns the distance in pixels appropriate for drawing a subsequent character after text.
+ *
+ * In Qt 5.12 and before the QFontMetrics::width() is used and it is deprecated since Qt 13.0.
+ * In Qt 5.11 the QFontMetrics::horizontalAdvance() was introduced.
+ */
+ int TextWidth(const QFontMetrics& fm, const QString& text);
} // namespace GUIUtil
#endif // BITCOIN_QT_GUIUTIL_H
diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts
index bff7469071..7864f97f31 100644
--- a/src/qt/locale/bitcoin_en.ts
+++ b/src/qt/locale/bitcoin_en.ts
@@ -132,7 +132,7 @@
<context>
<name>AddressTableModel</name>
<message>
- <location filename="../addresstablemodel.cpp" line="+163"/>
+ <location filename="../addresstablemodel.cpp" line="+165"/>
<source>Label</source>
<translation type="unfinished"></translation>
</message>
@@ -297,7 +297,7 @@
<context>
<name>BanTableModel</name>
<message>
- <location filename="../bantablemodel.cpp" line="+86"/>
+ <location filename="../bantablemodel.cpp" line="+88"/>
<source>IP/Netmask</source>
<translation type="unfinished"></translation>
</message>
@@ -310,17 +310,17 @@
<context>
<name>BitcoinGUI</name>
<message>
- <location filename="../bitcoingui.cpp" line="+318"/>
+ <location filename="../bitcoingui.cpp" line="+315"/>
<source>Sign &amp;message...</source>
<translation>Sign &amp;message...</translation>
</message>
<message>
- <location line="+638"/>
+ <location line="+637"/>
<source>Synchronizing with network...</source>
<translation>Synchronizing with network...</translation>
</message>
<message>
- <location line="-716"/>
+ <location line="-715"/>
<source>&amp;Overview</source>
<translation>&amp;Overview</translation>
</message>
@@ -400,7 +400,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+217"/>
+ <location line="+216"/>
<source>Wallet:</source>
<translation type="unfinished"></translation>
</message>
@@ -435,7 +435,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-1036"/>
+ <location line="-1035"/>
<source>Send coins to a Bitcoin address</source>
<translation>Send coins to a Bitcoin address</translation>
</message>
@@ -500,7 +500,7 @@
<translation>Verify messages to ensure they were signed with specified Bitcoin addresses</translation>
</message>
<message>
- <location line="+118"/>
+ <location line="+117"/>
<source>&amp;File</source>
<translation>&amp;File</translation>
</message>
@@ -520,7 +520,7 @@
<translation>Tabs toolbar</translation>
</message>
<message>
- <location line="-271"/>
+ <location line="-270"/>
<source>Request payments (generates QR codes and bitcoin: URIs)</source>
<translation type="unfinished"></translation>
</message>
@@ -545,7 +545,7 @@
<translation type="unfinished"></translation>
</message>
<message numerus="yes">
- <location line="+540"/>
+ <location line="+539"/>
<source>%n active connection(s) to Bitcoin network</source>
<translation>
<numerusform>%n active connection to Bitcoin network</numerusform>
@@ -606,7 +606,7 @@
<translation>Up to date</translation>
</message>
<message>
- <location line="-657"/>
+ <location line="-656"/>
<source>&amp;Sending addresses</source>
<translation type="unfinished"></translation>
</message>
@@ -641,7 +641,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+30"/>
+ <location line="+29"/>
<source>default wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -782,7 +782,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="+390"/>
+ <location filename="../bitcoin.cpp" line="+382"/>
<source>A fatal error occurred. Bitcoin can no longer continue safely and will quit.</source>
<translation type="unfinished"></translation>
</message>
@@ -941,7 +941,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+155"/>
+ <location line="+157"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
@@ -1736,14 +1736,14 @@
<location filename="../paymentserver.cpp" line="+226"/>
<location line="+346"/>
<location line="+42"/>
- <location line="+110"/>
+ <location line="+108"/>
<location line="+14"/>
<location line="+18"/>
<source>Payment request error</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-529"/>
+ <location line="-527"/>
<source>Cannot start bitcoin: click-to-pay handler</source>
<translation type="unfinished"></translation>
</message>
@@ -1805,12 +1805,12 @@
<location line="+31"/>
<location line="+10"/>
<location line="+17"/>
- <location line="+85"/>
+ <location line="+83"/>
<source>Payment request rejected</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-152"/>
+ <location line="-150"/>
<source>Payment request network doesn&apos;t match client network.</source>
<translation type="unfinished"></translation>
</message>
@@ -1841,7 +1841,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+65"/>
+ <location line="+63"/>
<source>Refund from %1</source>
<translation type="unfinished"></translation>
</message>
@@ -1879,7 +1879,7 @@
<context>
<name>PeerTableModel</name>
<message>
- <location filename="../peertablemodel.cpp" line="+108"/>
+ <location filename="../peertablemodel.cpp" line="+110"/>
<source>User Agent</source>
<translation type="unfinished"></translation>
</message>
@@ -1922,7 +1922,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+702"/>
+ <location line="+699"/>
<source>%1 d</source>
<translation type="unfinished"></translation>
</message>
@@ -1938,7 +1938,7 @@
</message>
<message>
<location line="+2"/>
- <location line="+50"/>
+ <location line="+47"/>
<source>%1 s</source>
<translation type="unfinished"></translation>
</message>
@@ -2032,27 +2032,22 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../bitcoin.cpp" line="+74"/>
- <source>Error parsing command line arguments: %1.</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+37"/>
+ <location filename="../bitcoin.cpp" line="+116"/>
<source>Error: Specified data directory &quot;%1&quot; does not exist.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+5"/>
+ <location line="+6"/>
<source>Error: Cannot parse configuration file: %1.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+14"/>
+ <location line="+15"/>
<source>Error: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+57"/>
+ <location line="+59"/>
<source>%1 didn&apos;t yet exit safely...</source>
<translation type="unfinished"></translation>
</message>
@@ -2103,7 +2098,7 @@
<context>
<name>RPCConsole</name>
<message>
- <location filename="../forms/debugwindow.ui" line="+56"/>
+ <location filename="../forms/debugwindow.ui" line="+75"/>
<location line="+26"/>
<location line="+26"/>
<location line="+26"/>
@@ -2147,12 +2142,12 @@
<translation>&amp;Information</translation>
</message>
<message>
- <location line="-10"/>
+ <location line="-29"/>
<source>Debug window</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+25"/>
+ <location line="+44"/>
<source>General</source>
<translation type="unfinished"></translation>
</message>
@@ -2265,8 +2260,8 @@
</message>
<message>
<location line="+65"/>
- <location filename="../rpcconsole.cpp" line="+498"/>
- <location line="+757"/>
+ <location filename="../rpcconsole.cpp" line="+497"/>
+ <location line="+759"/>
<source>Select a peer to view detailed information.</source>
<translation type="unfinished"></translation>
</message>
@@ -2417,7 +2412,7 @@
<translation>Clear console</translation>
</message>
<message>
- <location filename="../rpcconsole.cpp" line="-252"/>
+ <location filename="../rpcconsole.cpp" line="-243"/>
<source>1 &amp;hour</source>
<translation type="unfinished"></translation>
</message>
@@ -2450,7 +2445,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+47"/>
+ <location line="+38"/>
<source>&amp;Unban</source>
<translation type="unfinished"></translation>
</message>
@@ -2628,7 +2623,7 @@
<translation type="unfinished"></translation>
</message>
<message>
- <location filename="../receivecoinsdialog.cpp" line="+45"/>
+ <location filename="../receivecoinsdialog.cpp" line="+46"/>
<source>Copy URI</source>
<translation type="unfinished"></translation>
</message>
@@ -2714,7 +2709,7 @@
<context>
<name>RecentRequestsTableModel</name>
<message>
- <location filename="../recentrequeststablemodel.cpp" line="+25"/>
+ <location filename="../recentrequeststablemodel.cpp" line="+27"/>
<source>Date</source>
<translation type="unfinished">Date</translation>
</message>
@@ -2753,7 +2748,7 @@
<name>SendCoinsDialog</name>
<message>
<location filename="../forms/sendcoinsdialog.ui" line="+14"/>
- <location filename="../sendcoinsdialog.cpp" line="+600"/>
+ <location filename="../sendcoinsdialog.cpp" line="+601"/>
<source>Send Coins</source>
<translation>Send Coins</translation>
</message>
@@ -2940,7 +2935,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation>S&amp;end</translation>
</message>
<message>
- <location filename="../sendcoinsdialog.cpp" line="-512"/>
+ <location filename="../sendcoinsdialog.cpp" line="-513"/>
<source>Copy quantity</source>
<translation type="unfinished"></translation>
</message>
@@ -2980,7 +2975,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="+117"/>
+ <location line="+118"/>
<source> from wallet &apos;%1&apos;</source>
<translation type="unfinished"></translation>
</message>
@@ -3698,7 +3693,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<context>
<name>TransactionTableModel</name>
<message>
- <location filename="../transactiontablemodel.cpp" line="+223"/>
+ <location filename="../transactiontablemodel.cpp" line="+225"/>
<source>Date</source>
<translation type="unfinished">Date</translation>
</message>
@@ -3834,7 +3829,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<context>
<name>TransactionView</name>
<message>
- <location filename="../transactionview.cpp" line="+70"/>
+ <location filename="../transactionview.cpp" line="+69"/>
<location line="+16"/>
<source>All</source>
<translation type="unfinished"></translation>
@@ -3955,7 +3950,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="+199"/>
+ <location line="+194"/>
<source>Export Transaction History</source>
<translation type="unfinished"></translation>
</message>
@@ -4033,7 +4028,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<context>
<name>UnitDisplayStatusBarControl</name>
<message>
- <location filename="../bitcoingui.cpp" line="+155"/>
+ <location filename="../bitcoingui.cpp" line="+156"/>
<source>Unit to show amounts in. Click to select another unit.</source>
<translation type="unfinished"></translation>
</message>
@@ -4041,7 +4036,7 @@ 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="+70"/>
+ <location filename="../walletcontroller.cpp" line="+73"/>
<source>Close wallet</source>
<translation type="unfinished"></translation>
</message>
@@ -4072,7 +4067,7 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
<translation type="unfinished">Send Coins</translation>
</message>
<message>
- <location line="+301"/>
+ <location line="+309"/>
<location line="+39"/>
<location line="+5"/>
<source>Fee bump error</source>
@@ -4205,12 +4200,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="+31"/>
+ <location line="+30"/>
<source>Unable to start HTTP server. See debug log for details.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-168"/>
+ <location line="-167"/>
<source>The %s developers</source>
<translation type="unfinished"></translation>
</message>
@@ -4391,6 +4386,11 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
</message>
<message>
<location line="+4"/>
+ <source>Invalid P2P permission: &apos;%s&apos;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location line="+1"/>
<source>Invalid amount for -%s=&lt;amount&gt;: &apos;%s&apos;</source>
<translation type="unfinished"></translation>
</message>
@@ -4405,17 +4405,17 @@ 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="+23"/>
+ <location line="+22"/>
<source>Specified blocks directory &quot;%s&quot; does not exist.</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="+26"/>
+ <location line="+25"/>
<source>Upgrading txindex database</source>
<translation type="unfinished"></translation>
</message>
<message>
- <location line="-45"/>
+ <location line="-44"/>
<source>Loading P2P addresses...</source>
<translation type="unfinished"></translation>
</message>
@@ -4465,7 +4465,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="+6"/>
+ <location line="+5"/>
<source>Unable to bind to %s on this computer. %s is probably already running.</source>
<translation type="unfinished"></translation>
</message>
@@ -4500,7 +4500,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="-155"/>
+ <location line="-154"/>
<source>Error: Listening for incoming connections failed (listen returned error %s)</source>
<translation type="unfinished"></translation>
</message>
@@ -4545,7 +4545,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="+5"/>
<source>Invalid amount for -paytxfee=&lt;amount&gt;: &apos;%s&apos; (must be at least %s)</source>
<translation type="unfinished"></translation>
</message>
@@ -4555,7 +4555,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="+6"/>
+ <location line="+5"/>
<source>Need to specify a port with -whitebind: &apos;%s&apos;</source>
<translation type="unfinished"></translation>
</message>
@@ -4617,11 +4617,6 @@ Note: Since the fee is calculated on a per-byte basis, a fee of &quot;100 satos
</message>
<message>
<location line="+5"/>
- <source>Transaction too large for fee policy</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+1"/>
<source>Transaction too large</source>
<translation>Transaction too large</translation>
</message>
@@ -4661,7 +4656,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="-178"/>
+ <location line="-177"/>
<source>-maxtxfee is set very high! Fees this large could be paid on a single transaction.</source>
<translation type="unfinished"></translation>
</message>
@@ -4696,12 +4691,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="+20"/>
- <source>Keypool ran out, please call keypoolrefill first</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location line="+21"/>
+ <location line="+41"/>
<source>Starting network threads...</source>
<translation type="unfinished"></translation>
</message>
@@ -4736,12 +4726,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="+10"/>
+ <location line="+9"/>
<source>Unknown network specified in -onlynet: &apos;%s&apos;</source>
<translation>Unknown network specified in -onlynet: &apos;%s&apos;</translation>
</message>
<message>
- <location line="-51"/>
+ <location line="-50"/>
<source>Insufficient funds</source>
<translation>Insufficient funds</translation>
</message>
diff --git a/src/qt/macdockiconhandler.mm b/src/qt/macdockiconhandler.mm
index 102adce6c5..5eb23c76e6 100644
--- a/src/qt/macdockiconhandler.mm
+++ b/src/qt/macdockiconhandler.mm
@@ -1,12 +1,11 @@
-// Copyright (c) 2011-2018 The Bitcoin Core developers
+// Copyright (c) 2011-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "macdockiconhandler.h"
-#undef slots
-#include <objc/objc.h>
-#include <objc/message.h>
+#include <AppKit/AppKit.h>
+#include <objc/runtime.h>
static MacDockIconHandler *s_instance = nullptr;
@@ -21,9 +20,7 @@ bool dockClickHandler(id self, SEL _cmd, ...) {
}
void setupDockClickHandler() {
- id app = objc_msgSend((id)objc_getClass("NSApplication"), sel_registerName("sharedApplication"));
- id delegate = objc_msgSend(app, sel_registerName("delegate"));
- Class delClass = (Class)objc_msgSend(delegate, sel_registerName("class"));
+ Class delClass = (Class)[[[NSApplication sharedApplication] delegate] class];
SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:");
class_replaceMethod(delClass, shouldHandle, (IMP)dockClickHandler, "B@:");
}
@@ -44,3 +41,13 @@ void MacDockIconHandler::cleanup()
{
delete s_instance;
}
+
+/**
+ * Force application activation on macOS. With Qt 5.5.1 this is required when
+ * an action in the Dock menu is triggered.
+ * TODO: Define a Qt version where it's no-longer necessary.
+ */
+void ForceActivation()
+{
+ [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+}
diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp
index d8e48f350a..07ffff0126 100644
--- a/src/qt/overviewpage.cpp
+++ b/src/qt/overviewpage.cpp
@@ -204,9 +204,8 @@ void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly)
void OverviewPage::setClientModel(ClientModel *model)
{
this->clientModel = model;
- if(model)
- {
- // Show warning if this is a prerelease version
+ if (model) {
+ // Show warning, for example if this is a prerelease version
connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts);
updateAlerts(model->getStatusBarWarnings());
}
diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp
index f3f5d28af9..0bb87742e9 100644
--- a/src/qt/paymentserver.cpp
+++ b/src/qt/paymentserver.cpp
@@ -41,8 +41,8 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSslCertificate>
+#include <QSslConfiguration>
#include <QSslError>
-#include <QSslSocket>
#include <QStringList>
#include <QTextDocument>
#include <QUrlQuery>
@@ -448,9 +448,9 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store)
certList = QSslCertificate::fromPath(certFile);
// Use those certificates when fetching payment requests, too:
- QSslSocket::setDefaultCaCertificates(certList);
+ QSslConfiguration::defaultConfiguration().setCaCertificates(certList);
} else
- certList = QSslSocket::systemCaCertificates();
+ certList = QSslConfiguration::systemCaCertificates();
int nRootCerts = 0;
const QDateTime currentTime = QDateTime::currentDateTime();
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index 85b691c470..99a9a12fe2 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -11,6 +11,8 @@
#include <interfaces/node.h>
#include <sync.h>
+#include <algorithm>
+
#include <QDebug>
#include <QList>
#include <QTimer>
@@ -76,7 +78,7 @@ public:
if (sortColumn >= 0)
// sort cacheNodeStats (use stable sort to prevent rows jumping around unnecessarily)
- qStableSort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder));
+ std::stable_sort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder));
// build index map
mapNodeRows.clear();
diff --git a/src/qt/platformstyle.cpp b/src/qt/platformstyle.cpp
index fca2a4e8c5..08d692e44c 100644
--- a/src/qt/platformstyle.cpp
+++ b/src/qt/platformstyle.cpp
@@ -114,11 +114,6 @@ QIcon PlatformStyle::SingleColorIcon(const QIcon& icon) const
return ColorizeIcon(icon, SingleColor());
}
-QIcon PlatformStyle::TextColorIcon(const QString& filename) const
-{
- return ColorizeIcon(filename, TextColor());
-}
-
QIcon PlatformStyle::TextColorIcon(const QIcon& icon) const
{
return ColorizeIcon(icon, TextColor());
diff --git a/src/qt/platformstyle.h b/src/qt/platformstyle.h
index 4e763e760e..635aec4c93 100644
--- a/src/qt/platformstyle.h
+++ b/src/qt/platformstyle.h
@@ -33,9 +33,6 @@ public:
/** Colorize an icon (given object) with the icon color */
QIcon SingleColorIcon(const QIcon& icon) const;
- /** Colorize an icon (given filename) with the text color */
- QIcon TextColorIcon(const QString& filename) const;
-
/** Colorize an icon (given object) with the text color */
QIcon TextColorIcon(const QIcon& icon) const;
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 05157c2a4a..e8cf432131 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -261,7 +261,7 @@ void ReceiveCoinsDialog::copyColumnToClipboard(int column)
if (!firstIndex.isValid()) {
return;
}
- GUIUtil::setClipboard(model->getRecentRequestsTableModel()->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole).toString());
+ GUIUtil::setClipboard(model->getRecentRequestsTableModel()->index(firstIndex.row(), column).data(Qt::EditRole).toString());
}
// context menu
diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp
index aa746017f3..1611ec823c 100644
--- a/src/qt/recentrequeststablemodel.cpp
+++ b/src/qt/recentrequeststablemodel.cpp
@@ -11,6 +11,8 @@
#include <clientversion.h>
#include <streams.h>
+#include <algorithm>
+
RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) :
QAbstractTableModel(parent), walletModel(parent)
@@ -202,7 +204,7 @@ void RecentRequestsTableModel::addNewRequest(RecentRequestEntry &recipient)
void RecentRequestsTableModel::sort(int column, Qt::SortOrder order)
{
- qSort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order));
+ std::sort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order));
Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex()));
}
diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h
index 8a1140e952..130b709d46 100644
--- a/src/qt/recentrequeststablemodel.h
+++ b/src/qt/recentrequeststablemodel.h
@@ -76,7 +76,7 @@ public:
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
- QModelIndex index(int row, int column, const QModelIndex &parent) const;
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
Qt::ItemFlags flags(const QModelIndex &index) const;
/*@}*/
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 19b11ba1cd..eccc34e12f 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -28,13 +28,12 @@
#include <wallet/wallet.h>
#endif
-#include <QDesktopWidget>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QScrollBar>
+#include <QScreen>
#include <QSettings>
-#include <QSignalMapper>
#include <QTime>
#include <QTimer>
#include <QStringList>
@@ -451,7 +450,7 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
QSettings settings;
if (!restoreGeometry(settings.value("RPCConsoleWindowGeometry").toByteArray())) {
// Restore failed (perhaps missing setting), center the window
- move(QApplication::desktop()->availableGeometry().center() - frameGeometry().center());
+ move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
}
QChar nonbreaking_hyphen(8209);
@@ -558,6 +557,17 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event)
void RPCConsole::setClientModel(ClientModel *model)
{
clientModel = model;
+
+ bool wallet_enabled{false};
+#ifdef ENABLE_WALLET
+ wallet_enabled = WalletModel::isWalletEnabled();
+#endif // ENABLE_WALLET
+ if (model && !wallet_enabled) {
+ // Show warning, for example if this is a prerelease version
+ connect(model, &ClientModel::alertsChanged, this, &RPCConsole::updateAlerts);
+ updateAlerts(model->getStatusBarWarnings());
+ }
+
ui->trafficGraph->setClientModel(model);
if (model && clientModel->getPeerTableModel() && clientModel->getBanTableModel()) {
// Keep up to date with client
@@ -603,19 +613,10 @@ void RPCConsole::setClientModel(ClientModel *model)
peersTableContextMenu->addAction(banAction7d);
peersTableContextMenu->addAction(banAction365d);
- // Add a signal mapping to allow dynamic context menu arguments.
- // We need to use int (instead of int64_t), because signal mapper only supports
- // int or objects, which is okay because max bantime (1 year) is < int_max.
- QSignalMapper* signalMapper = new QSignalMapper(this);
- signalMapper->setMapping(banAction1h, 60*60);
- signalMapper->setMapping(banAction24h, 60*60*24);
- signalMapper->setMapping(banAction7d, 60*60*24*7);
- signalMapper->setMapping(banAction365d, 60*60*24*365);
- connect(banAction1h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
- connect(banAction24h, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
- connect(banAction7d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
- connect(banAction365d, &QAction::triggered, signalMapper, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
- connect(signalMapper, static_cast<void (QSignalMapper::*)(int)>(&QSignalMapper::mapped), this, &RPCConsole::banSelectedNode);
+ connect(banAction1h, &QAction::triggered, [this] { banSelectedNode(60 * 60); });
+ connect(banAction24h, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24); });
+ connect(banAction7d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 7); });
+ connect(banAction365d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 365); });
// peer table context menu signals
connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu);
@@ -1274,3 +1275,9 @@ QString RPCConsole::tabTitle(TabTypes tab_type) const
{
return ui->tabWidget->tabText(tab_type);
}
+
+void RPCConsole::updateAlerts(const QString& warnings)
+{
+ this->ui->label_alerts->setVisible(!warnings.isEmpty());
+ this->ui->label_alerts->setText(warnings);
+}
diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h
index 38015e38fd..3f7a74ba03 100644
--- a/src/qt/rpcconsole.h
+++ b/src/qt/rpcconsole.h
@@ -167,6 +167,9 @@ private:
/** Update UI with latest network info from model. */
void updateNetworkState();
+
+private Q_SLOTS:
+ void updateAlerts(const QString& warnings);
};
#endif // BITCOIN_QT_RPCCONSOLE_H
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 193fba78b1..a88119d8c5 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -230,8 +230,9 @@ void SendCoinsDialog::on_sendButton_clicked()
{
recipients.append(entry->getValue());
}
- else
+ else if (valid)
{
+ ui->scrollArea->ensureWidgetVisible(entry);
valid = false;
}
}
@@ -282,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
@@ -296,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
@@ -703,7 +704,7 @@ void SendCoinsDialog::updateSmartFeeLabel()
int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
- ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
+ ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
}
else
{
diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp
index 5bceb1f945..0e5abb89f3 100644
--- a/src/qt/splashscreen.cpp
+++ b/src/qt/splashscreen.cpp
@@ -8,12 +8,12 @@
#include <qt/splashscreen.h>
-#include <qt/networkstyle.h>
-
#include <clientversion.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <interfaces/wallet.h>
+#include <qt/guiutil.h>
+#include <qt/networkstyle.h>
#include <ui_interface.h>
#include <util/system.h>
#include <util/translation.h>
@@ -21,9 +21,9 @@
#include <QApplication>
#include <QCloseEvent>
-#include <QDesktopWidget>
#include <QPainter>
#include <QRadialGradient>
+#include <QScreen>
SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const NetworkStyle *networkStyle) :
@@ -75,21 +75,21 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw
// check font size and drawing with
pixPaint.setFont(QFont(font, 33*fontFactor));
QFontMetrics fm = pixPaint.fontMetrics();
- int titleTextWidth = fm.width(titleText);
+ int titleTextWidth = GUIUtil::TextWidth(fm, titleText);
if (titleTextWidth > 176) {
fontFactor = fontFactor * 176 / titleTextWidth;
}
pixPaint.setFont(QFont(font, 33*fontFactor));
fm = pixPaint.fontMetrics();
- titleTextWidth = fm.width(titleText);
+ titleTextWidth = GUIUtil::TextWidth(fm, titleText);
pixPaint.drawText(pixmap.width()/devicePixelRatio-titleTextWidth-paddingRight,paddingTop,titleText);
pixPaint.setFont(QFont(font, 15*fontFactor));
// if the version string is too long, reduce size
fm = pixPaint.fontMetrics();
- int versionTextWidth = fm.width(versionText);
+ int versionTextWidth = GUIUtil::TextWidth(fm, versionText);
if(versionTextWidth > titleTextWidth+paddingRight-10) {
pixPaint.setFont(QFont(font, 10*fontFactor));
titleVersionVSpace -= 5;
@@ -111,7 +111,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw
boldFont.setWeight(QFont::Bold);
pixPaint.setFont(boldFont);
fm = pixPaint.fontMetrics();
- int titleAddTextWidth = fm.width(titleAddText);
+ int titleAddTextWidth = GUIUtil::TextWidth(fm, titleAddText);
pixPaint.drawText(pixmap.width()/devicePixelRatio-titleAddTextWidth-10,15,titleAddText);
}
@@ -124,7 +124,7 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw
QRect r(QPoint(), QSize(pixmap.size().width()/devicePixelRatio,pixmap.size().height()/devicePixelRatio));
resize(r.size());
setFixedSize(r.size());
- move(QApplication::desktop()->screenGeometry().center() - r.center());
+ move(QGuiApplication::primaryScreen()->geometry().center() - r.center());
subscribeToCoreSignals();
installEventFilter(this);
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index 1064c60dfd..8d0cb54151 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -17,6 +17,8 @@
#include <interfaces/handler.h>
#include <uint256.h>
+#include <algorithm>
+
#include <QColor>
#include <QDateTime>
#include <QDebug>
@@ -93,9 +95,9 @@ public:
qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
// Find bounds of this transaction in model
- QList<TransactionRecord>::iterator lower = qLowerBound(
+ QList<TransactionRecord>::iterator lower = std::lower_bound(
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
- QList<TransactionRecord>::iterator upper = qUpperBound(
+ QList<TransactionRecord>::iterator upper = std::upper_bound(
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
int lowerIndex = (lower - cachedWallet.begin());
int upperIndex = (upper - cachedWallet.begin());
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 17e174e57a..cbc4ab49f5 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -30,7 +30,6 @@
#include <QMenu>
#include <QPoint>
#include <QScrollBar>
-#include <QSignalMapper>
#include <QTableView>
#include <QTimer>
#include <QUrl>
@@ -176,11 +175,6 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
contextMenu->addAction(abandonAction);
contextMenu->addAction(editLabelAction);
- mapperThirdPartyTxUrls = new QSignalMapper(this);
-
- // Connect actions
- connect(mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)(const QString&)>(&QSignalMapper::mapped), this, &TransactionView::openThirdPartyTxUrl);
-
connect(dateWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate);
connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType);
connect(watchOnlyWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseWatchonly);
@@ -246,15 +240,15 @@ void TransactionView::setModel(WalletModel *_model)
QStringList listUrls = _model->getOptionsModel()->getThirdPartyTxUrls().split("|", QString::SkipEmptyParts);
for (int i = 0; i < listUrls.size(); ++i)
{
- QString host = QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host();
+ QString url = listUrls[i].trimmed();
+ QString host = QUrl(url, QUrl::StrictMode).host();
if (!host.isEmpty())
{
QAction *thirdPartyTxUrlAction = new QAction(host, this); // use host as menu item label
if (i == 0)
contextMenu->addSeparator();
contextMenu->addAction(thirdPartyTxUrlAction);
- connect(thirdPartyTxUrlAction, &QAction::triggered, mapperThirdPartyTxUrls, static_cast<void (QSignalMapper::*)()>(&QSignalMapper::map));
- mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction, listUrls[i].trimmed());
+ connect(thirdPartyTxUrlAction, &QAction::triggered, [this, url] { openThirdPartyTxUrl(url); });
}
}
}
diff --git a/src/qt/transactionview.h b/src/qt/transactionview.h
index e07181d1c8..79347c371f 100644
--- a/src/qt/transactionview.h
+++ b/src/qt/transactionview.h
@@ -23,7 +23,6 @@ class QFrame;
class QLineEdit;
class QMenu;
class QModelIndex;
-class QSignalMapper;
class QTableView;
QT_END_NAMESPACE
@@ -72,7 +71,6 @@ private:
QLineEdit *amountWidget;
QMenu *contextMenu;
- QSignalMapper *mapperThirdPartyTxUrls;
QFrame *dateRangeWidget;
QDateTimeEdit *dateFrom;
diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp
index a8e7bce6b5..8b8283d3d8 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 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,147 @@ 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::askPasshprase()
+{
+ m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase);
+ 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->disablePrivateKeys()) {
+ 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->blank()) {
+ 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->encrypt()) {
+ askPasshprase();
+ } 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..4e1a772f3a 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 askPasshprase();
+ 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 bfb6ab6a21..9513c2b9ac 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -10,6 +10,7 @@
#include <chain.h>
#include <chainparams.h>
#include <coins.h>
+#include <node/coinstats.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <hash.h>
@@ -374,6 +375,7 @@ static std::string EntryDescriptionString()
return " \"vsize\" : n, (numeric) virtual transaction size as defined in BIP 141. This is different from actual serialized size for witness transactions as witness data is discounted.\n"
" \"size\" : n, (numeric) (DEPRECATED) same as vsize. Only returned if bitcoind is started with -deprecatedrpc=size\n"
" size will be completely removed in v0.20.\n"
+ " \"weight\" : n, (numeric) transaction weight as defined in BIP 141.\n"
" \"fee\" : n, (numeric) transaction fee in " + CURRENCY_UNIT + " (DEPRECATED)\n"
" \"modifiedfee\" : n, (numeric) transaction fee with fee deltas used for mining priority (DEPRECATED)\n"
" \"time\" : n, (numeric) local time transaction entered pool in seconds since 1 Jan 1970 GMT\n"
@@ -413,6 +415,7 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
info.pushKV("vsize", (int)e.GetTxSize());
if (IsDeprecatedRPCEnabled("size")) info.pushKV("size", (int)e.GetTxSize());
+ info.pushKV("weight", (int)e.GetTxWeight());
info.pushKV("fee", ValueFromAmount(e.GetFee()));
info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
info.pushKV("time", e.GetTime());
@@ -907,77 +910,6 @@ static UniValue getblock(const JSONRPCRequest& request)
return blockToJSON(block, tip, pblockindex, verbosity >= 2);
}
-struct CCoinsStats
-{
- int nHeight;
- uint256 hashBlock;
- uint64_t nTransactions;
- uint64_t nTransactionOutputs;
- uint64_t nBogoSize;
- uint256 hashSerialized;
- uint64_t nDiskSize;
- CAmount nTotalAmount;
-
- CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {}
-};
-
-static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs)
-{
- assert(!outputs.empty());
- ss << hash;
- ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u);
- stats.nTransactions++;
- for (const auto& output : outputs) {
- ss << VARINT(output.first + 1);
- ss << output.second.out.scriptPubKey;
- ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
- stats.nTransactionOutputs++;
- stats.nTotalAmount += output.second.out.nValue;
- stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
- 2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
- }
- ss << VARINT(0u);
-}
-
-//! Calculate statistics about the unspent transaction output set
-static bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats)
-{
- std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor());
- assert(pcursor);
-
- CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION);
- stats.hashBlock = pcursor->GetBestBlock();
- {
- LOCK(cs_main);
- stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight;
- }
- ss << stats.hashBlock;
- uint256 prevkey;
- std::map<uint32_t, Coin> outputs;
- while (pcursor->Valid()) {
- boost::this_thread::interruption_point();
- COutPoint key;
- Coin coin;
- if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
- if (!outputs.empty() && key.hash != prevkey) {
- ApplyStats(stats, ss, prevkey, outputs);
- outputs.clear();
- }
- prevkey = key.hash;
- outputs[key.n] = std::move(coin);
- } else {
- return error("%s: unable to read value", __func__);
- }
- pcursor->Next();
- }
- if (!outputs.empty()) {
- ApplyStats(stats, ss, prevkey, outputs);
- }
- stats.hashSerialized = ss.GetHash();
- stats.nDiskSize = view->EstimateSize();
- return true;
-}
-
static UniValue pruneblockchain(const JSONRPCRequest& request)
{
RPCHelpMan{"pruneblockchain", "",
@@ -1637,6 +1569,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request)
" \"time\": xxxxx, (numeric) The timestamp for the final block in the window in UNIX format.\n"
" \"txcount\": xxxxx, (numeric) The total number of transactions in the chain up to that point.\n"
" \"window_final_block_hash\": \"...\", (string) The hash of the final block in the window.\n"
+ " \"window_final_block_height\": xxxxx, (numeric) The height of the final block in the window.\n"
" \"window_block_count\": xxxxx, (numeric) Size of the window in number of blocks.\n"
" \"window_tx_count\": xxxxx, (numeric) The number of transactions in the window. Only returned if \"window_block_count\" is > 0.\n"
" \"window_interval\": xxxxx, (numeric) The elapsed time in the window in seconds. Only returned if \"window_block_count\" is > 0.\n"
@@ -1687,6 +1620,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request)
ret.pushKV("time", (int64_t)pindex->nTime);
ret.pushKV("txcount", (int64_t)pindex->nChainTx);
ret.pushKV("window_final_block_hash", pindex->GetBlockHash().GetHex());
+ ret.pushKV("window_final_block_height", pindex->nHeight);
ret.pushKV("window_block_count", blockcount);
if (blockcount > 0) {
ret.pushKV("window_tx_count", nTxDiff);
@@ -2130,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{""},
@@ -2194,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 3cd661e067..93fca5a6de 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -85,6 +85,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getblockheader", 1, "verbose" },
{ "getchaintxstats", 0, "nblocks" },
{ "gettransaction", 1, "include_watchonly" },
+ { "gettransaction", 2, "decode" },
{ "getrawtransaction", 1, "verbose" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index ffbad45714..fb8ea8c227 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -758,7 +758,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)
diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp
index 55425cca35..697c6d45c4 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,7 +196,7 @@ 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())) {
+ if (keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
RPCTypeCheckObj(prevOut,
{
{"redeemScript", UniValueType(UniValue::VSTR)},
@@ -226,7 +225,10 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival
}
}
}
+}
+UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, std::map<COutPoint, Coin>& coins, const UniValue& hashType)
+{
int nHashType = ParseSighashString(hashType);
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h
index c85593e71e..b35e6da4ca 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 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, 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/util.cpp b/src/rpc/util.cpp
index 464537a5b0..22d67c34da 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -4,11 +4,12 @@
#include <key_io.h>
#include <outputtype.h>
-#include <script/signingprovider.h>
#include <rpc/util.h>
#include <script/descriptor.h>
+#include <script/signingprovider.h>
#include <tinyformat.h>
#include <util/strencodings.h>
+#include <util/string.h>
#include <tuple>
@@ -645,11 +646,7 @@ std::string RPCArg::ToString(const bool oneline) const
}
case Type::OBJ:
case Type::OBJ_USER_KEYS: {
- std::string res;
- for (size_t i = 0; i < m_inner.size();) {
- res += m_inner[i].ToStringObj(oneline);
- if (++i < m_inner.size()) res += ",";
- }
+ const std::string res = Join(m_inner, ",", [&](const RPCArg& i) { return i.ToStringObj(oneline); });
if (m_type == Type::OBJ) {
return "{" + res + "}";
} else {
diff --git a/src/serialize.h b/src/serialize.h
index 1dc27d84eb..a38d76fc18 100644
--- a/src/serialize.h
+++ b/src/serialize.h
@@ -555,6 +555,7 @@ template<typename Stream, unsigned int N, typename T> inline void Unserialize(St
* vectors of unsigned char are a special case and are intended to be serialized as a single opaque blob.
*/
template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const unsigned char&);
+template<typename Stream, typename T, typename A> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const bool&);
template<typename Stream, typename T, typename A, typename V> void Serialize_impl(Stream& os, const std::vector<T, A>& v, const V&);
template<typename Stream, typename T, typename A> inline void Serialize(Stream& os, const std::vector<T, A>& v);
template<typename Stream, typename T, typename A> void Unserialize_impl(Stream& is, std::vector<T, A>& v, const unsigned char&);
@@ -713,6 +714,18 @@ void Serialize_impl(Stream& os, const std::vector<T, A>& v, const unsigned char&
os.write((char*)v.data(), v.size() * sizeof(T));
}
+template<typename Stream, typename T, typename A>
+void Serialize_impl(Stream& os, const std::vector<T, A>& v, const bool&)
+{
+ // A special case for std::vector<bool>, as dereferencing
+ // std::vector<bool>::const_iterator does not result in a const bool&
+ // due to std::vector's special casing for bool arguments.
+ WriteCompactSize(os, v.size());
+ for (bool elem : v) {
+ ::Serialize(os, elem);
+ }
+}
+
template<typename Stream, typename T, typename A, typename V>
void Serialize_impl(Stream& os, const std::vector<T, A>& v, const V&)
{
diff --git a/src/test/README.md b/src/test/README.md
index 0017e3de26..8901fae7bd 100644
--- a/src/test/README.md
+++ b/src/test/README.md
@@ -49,7 +49,3 @@ unit tests. The file naming convention is `<source_filename>_tests.cpp`
and such files should wrap their tests in a test suite
called `<source_filename>_tests`. For an example of this pattern,
examine `uint256_tests.cpp`.
-
-For further reading, I found the following website to be helpful in
-explaining how the boost unit test framework works:
-[http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/](http://archive.is/dRBGf).
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/descriptor_tests.cpp b/src/test/descriptor_tests.cpp
index 1b9ec76385..50ac0bd7b8 100644
--- a/src/test/descriptor_tests.cpp
+++ b/src/test/descriptor_tests.cpp
@@ -42,33 +42,47 @@ bool EqualDescriptor(std::string a, std::string b)
return a == b;
}
-std::string MaybeUseHInsteadOfApostrophy(std::string ret)
+std::string UseHInsteadOfApostrophe(const std::string& desc)
{
- if (InsecureRandBool()) {
- while (true) {
- auto it = ret.find("'");
- if (it != std::string::npos) {
- ret[it] = 'h';
- if (ret.size() > 9 && ret[ret.size() - 9] == '#') ret = ret.substr(0, ret.size() - 9); // Changing apostrophe to h breaks the checksum
- } else {
- break;
- }
- }
+ std::string ret = desc;
+ while (true) {
+ auto it = ret.find('\'');
+ if (it == std::string::npos) break;
+ ret[it] = 'h';
+ }
+
+ // GetDescriptorChecksum returns "" if the checksum exists but is bad.
+ // Switching apostrophes with 'h' breaks the checksum if it exists - recalculate it and replace the broken one.
+ if (GetDescriptorChecksum(ret) == "") {
+ ret = ret.substr(0, desc.size() - 9);
+ ret += std::string("#") + GetDescriptorChecksum(ret);
}
return ret;
}
const std::set<std::vector<uint32_t>> ONLY_EMPTY{{}};
-void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
+void DoCheck(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY,
+ bool replace_apostrophe_with_h_in_prv=false, bool replace_apostrophe_with_h_in_pub=false)
{
FlatSigningProvider keys_priv, keys_pub;
std::set<std::vector<uint32_t>> left_paths = paths;
std::string error;
+ std::unique_ptr<Descriptor> parse_priv;
+ std::unique_ptr<Descriptor> parse_pub;
// Check that parsing succeeds.
- auto parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv, error);
- auto parse_pub = Parse(MaybeUseHInsteadOfApostrophy(pub), keys_pub, error);
+ if (replace_apostrophe_with_h_in_prv) {
+ parse_priv = Parse(UseHInsteadOfApostrophe(prv), keys_priv, error);
+ } else {
+ parse_priv = Parse(prv, keys_priv, error);
+ }
+ if (replace_apostrophe_with_h_in_pub) {
+ parse_pub = Parse(UseHInsteadOfApostrophe(pub), keys_pub, error);
+ } else {
+ parse_pub = Parse(pub, keys_pub, error);
+ }
+
BOOST_CHECK(parse_priv);
BOOST_CHECK(parse_pub);
@@ -167,6 +181,32 @@ void Check(const std::string& prv, const std::string& pub, int flags, const std:
BOOST_CHECK_MESSAGE(left_paths.empty(), "Not all expected key paths found: " + prv);
}
+void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts, const std::set<std::vector<uint32_t>>& paths = ONLY_EMPTY)
+{
+ bool found_apostrophes_in_prv = false;
+ bool found_apostrophes_in_pub = false;
+
+ // Do not replace apostrophes with 'h' in prv and pub
+ DoCheck(prv, pub, flags, scripts, paths);
+
+ // Replace apostrophes with 'h' in prv but not in pub, if apostrophes are found in prv
+ if (prv.find('\'') != std::string::npos) {
+ found_apostrophes_in_prv = true;
+ DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */false);
+ }
+
+ // Replace apostrophes with 'h' in pub but not in prv, if apostrophes are found in pub
+ if (pub.find('\'') != std::string::npos) {
+ found_apostrophes_in_pub = true;
+ DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */false, /*replace_apostrophe_with_h_in_pub = */true);
+ }
+
+ // Replace apostrophes with 'h' both in prv and in pub, if apostrophes are found in both
+ if (found_apostrophes_in_prv && found_apostrophes_in_pub) {
+ DoCheck(prv, pub, flags, scripts, paths, /* replace_apostrophe_with_h_in_prv = */true, /*replace_apostrophe_with_h_in_pub = */true);
+ }
+}
+
}
BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp
index 8a8620938e..b90be15fba 100644
--- a/src/test/serialize_tests.cpp
+++ b/src/test/serialize_tests.cpp
@@ -258,6 +258,14 @@ static bool isCanonicalException(const std::ios_base::failure& ex)
return strcmp(expectedException.what(), ex.what()) == 0;
}
+BOOST_AUTO_TEST_CASE(vector_bool)
+{
+ std::vector<uint8_t> vec1{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1};
+ std::vector<bool> vec2{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1};
+
+ BOOST_CHECK(vec1 == std::vector<uint8_t>(vec2.begin(), vec2.end()));
+ BOOST_CHECK(SerializeHash(vec1) == SerializeHash(vec2));
+}
BOOST_AUTO_TEST_CASE(noncanonical)
{
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index e69ebcc2c3..193858cca9 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -13,7 +13,7 @@
#include <boost/test/unit_test.hpp>
-bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks);
+bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks);
BOOST_AUTO_TEST_SUITE(tx_validationcache_tests)
@@ -125,7 +125,7 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
// WITNESS requires P2SH
test_flags |= SCRIPT_VERIFY_P2SH;
}
- bool ret = CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, nullptr);
+ bool ret = CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, nullptr);
// CheckInputs should succeed iff test_flags doesn't intersect with
// failing_flags
bool expected_return_value = !(test_flags & failing_flags);
@@ -135,13 +135,13 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
if (ret && add_to_cache) {
// Check that we get a cache hit if the tx was valid
std::vector<CScriptCheck> scriptchecks;
- BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, &scriptchecks));
+ BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks));
BOOST_CHECK(scriptchecks.empty());
} else {
// Check that we get script executions to check, if the transaction
// was invalid, or we didn't add to cache.
std::vector<CScriptCheck> scriptchecks;
- BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), true, test_flags, true, add_to_cache, txdata, &scriptchecks));
+ BOOST_CHECK(CheckInputs(tx, state, &::ChainstateActive().CoinsTip(), test_flags, true, add_to_cache, txdata, &scriptchecks));
BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
}
}
@@ -204,13 +204,13 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
CValidationState state;
PrecomputedTransactionData ptd_spend_tx(spend_tx);
- BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr));
+ BOOST_CHECK(!CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr));
// If we call again asking for scriptchecks (as happens in
// ConnectBlock), we should add a script check object for this -- we're
// not caching invalidity (if that changes, delete this test case).
std::vector<CScriptCheck> scriptchecks;
- BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks));
+ BOOST_CHECK(CheckInputs(CTransaction(spend_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks));
BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);
// Test that CheckInputs returns true iff DERSIG-enforcing flags are
@@ -272,7 +272,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
CValidationState state;
PrecomputedTransactionData txdata(invalid_with_cltv_tx);
- BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr));
+ BOOST_CHECK(CheckInputs(CTransaction(invalid_with_cltv_tx), state, ::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr));
}
// TEST CHECKSEQUENCEVERIFY
@@ -300,7 +300,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
CValidationState state;
PrecomputedTransactionData txdata(invalid_with_csv_tx);
- BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr));
+ BOOST_CHECK(CheckInputs(CTransaction(invalid_with_csv_tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr));
}
// TODO: add tests for remaining script flags
@@ -362,12 +362,12 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
CValidationState state;
PrecomputedTransactionData txdata(tx);
// This transaction is now invalid under segwit, because of the second input.
- BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr));
+ BOOST_CHECK(!CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr));
std::vector<CScriptCheck> scriptchecks;
// Make sure this transaction was not cached (ie because the first
// input was valid)
- BOOST_CHECK(CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks));
+ BOOST_CHECK(CheckInputs(CTransaction(tx), state, &::ChainstateActive().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks));
// Should get 2 script checks back -- caching is on a whole-transaction basis.
BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
}
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index 7119f56fc3..65cb956fbe 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -6,11 +6,12 @@
#include <clientversion.h>
#include <sync.h>
+#include <test/setup_common.h>
#include <test/util.h>
-#include <util/strencodings.h>
#include <util/moneystr.h>
+#include <util/strencodings.h>
+#include <util/string.h>
#include <util/time.h>
-#include <test/setup_common.h>
#include <stdint.h>
#include <thread>
@@ -123,6 +124,19 @@ BOOST_AUTO_TEST_CASE(util_HexStr)
);
}
+BOOST_AUTO_TEST_CASE(util_Join)
+{
+ // Normal version
+ BOOST_CHECK_EQUAL(Join({}, ", "), "");
+ BOOST_CHECK_EQUAL(Join({"foo"}, ", "), "foo");
+ BOOST_CHECK_EQUAL(Join({"foo", "bar"}, ", "), "foo, bar");
+
+ // Version with unary operator
+ const auto op_upper = [](const std::string& s) { return ToUpper(s); };
+ BOOST_CHECK_EQUAL(Join<std::string>({}, ", ", op_upper), "");
+ BOOST_CHECK_EQUAL(Join<std::string>({"foo"}, ", ", op_upper), "FOO");
+ BOOST_CHECK_EQUAL(Join<std::string>({"foo", "bar"}, ", ", op_upper), "FOO, BAR");
+}
BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime)
{
diff --git a/src/util/error.cpp b/src/util/error.cpp
index 287476c0d3..aa44ed3e3a 100644
--- a/src/util/error.cpp
+++ b/src/util/error.cpp
@@ -41,12 +41,12 @@ std::string ResolveErrMsg(const std::string& optname, const std::string& strBind
return strprintf(_("Cannot resolve -%s address: '%s'").translated, optname, strBind);
}
-std::string AmountHighWarn(const std::string& optname)
+bilingual_str AmountHighWarn(const std::string& optname)
{
- return strprintf(_("%s is set very high!").translated, optname);
+ return strprintf(_("%s is set very high!"), optname);
}
-std::string AmountErrMsg(const std::string& optname, const std::string& strValue)
+bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue)
{
- return strprintf(_("Invalid amount for -%s=<amount>: '%s'").translated, optname, strValue);
+ return strprintf(_("Invalid amount for -%s=<amount>: '%s'"), optname, strValue);
}
diff --git a/src/util/error.h b/src/util/error.h
index 7777cc0c5d..f540b0020d 100644
--- a/src/util/error.h
+++ b/src/util/error.h
@@ -17,6 +17,8 @@
#include <string>
+struct bilingual_str;
+
enum class TransactionError {
OK, //!< No error
MISSING_INPUTS,
@@ -34,8 +36,8 @@ std::string TransactionErrorString(const TransactionError error);
std::string ResolveErrMsg(const std::string& optname, const std::string& strBind);
-std::string AmountHighWarn(const std::string& optname);
+bilingual_str AmountHighWarn(const std::string& optname);
-std::string AmountErrMsg(const std::string& optname, const std::string& strValue);
+bilingual_str AmountErrMsg(const std::string& optname, const std::string& strValue);
#endif // BITCOIN_UTIL_ERROR_H
diff --git a/src/util/string.cpp b/src/util/string.cpp
new file mode 100644
index 0000000000..8ea3a1afc6
--- /dev/null
+++ b/src/util/string.cpp
@@ -0,0 +1,5 @@
+// Copyright (c) 2019 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <util/string.h>
diff --git a/src/util/string.h b/src/util/string.h
new file mode 100644
index 0000000000..dec0c19b08
--- /dev/null
+++ b/src/util/string.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_UTIL_STRING_H
+#define BITCOIN_UTIL_STRING_H
+
+#include <functional>
+#include <string>
+#include <vector>
+
+/**
+ * Join a list of items
+ *
+ * @param list The list to join
+ * @param separator The separator
+ * @param unary_op Apply this operator to each item in the list
+ */
+template <typename T, typename UnaryOp>
+std::string Join(const std::vector<T>& list, const std::string& separator, UnaryOp unary_op)
+{
+ std::string ret;
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (i > 0) ret += separator;
+ ret += unary_op(list.at(i));
+ }
+ return ret;
+}
+
+inline std::string Join(const std::vector<std::string>& list, const std::string& separator)
+{
+ return Join(list, separator, [](const std::string& i) { return i; });
+}
+
+#endif // BITCOIN_UTIL_STRENCODINGS_H
diff --git a/src/validation.cpp b/src/validation.cpp
index 74f68a3047..d470fd5b6e 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -184,7 +184,7 @@ std::unique_ptr<CBlockTreeDB> pblocktree;
// See definition for documentation
static void FindFilesToPruneManual(std::set<int>& setFilesToPrune, int nManualPruneHeight);
static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfterHeight);
-bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr);
+bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks = nullptr);
static FILE* OpenUndoFile(const FlatFilePos &pos, bool fReadOnly = false);
static FlatFileSeq BlockFileSeq();
static FlatFileSeq UndoFileSeq();
@@ -425,7 +425,7 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, CValidationSt
}
}
- return CheckInputs(tx, state, view, true, flags, cacheSigStore, true, txdata);
+ return CheckInputs(tx, state, view, flags, cacheSigStore, true, txdata);
}
/**
@@ -615,17 +615,55 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
REJECT_HIGHFEE, "absurdly-high-fee",
strprintf("%d > %d", nFees, nAbsurdFee));
+ const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
// 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;
+
+ 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 nLimitDescendants 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();
+
+ nLimitDescendants += 1;
+ nLimitDescendantSize += conflict->GetSizeWithDescendants();
+ }
+
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;
+ // 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
@@ -674,7 +712,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
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.
@@ -734,6 +771,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
// 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 -
@@ -773,15 +815,17 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
// Check against previous transactions
- // This is done last to help prevent CPU exhaustion denial-of-service attacks.
+ // 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, true, scriptVerifyFlags, true, false, txdata)) {
+ 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, true, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, txdata) &&
- !CheckInputs(tx, stateDummy, view, true, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, txdata)) {
+ 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());
@@ -1298,90 +1342,79 @@ void InitScriptExecutionCache() {
*
* Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp
*/
-bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
- if (!tx.IsCoinBase())
- {
- if (pvChecks)
- pvChecks->reserve(tx.vin.size());
-
- // 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 attacks.
-
- // Skip script verification when connecting blocks under the
- // assumevalid block. Assuming the assumevalid block is valid this
- // is safe because block merkle hashes are still computed and checked,
- // Of course, if an assumed valid block is invalid due to false scriptSigs
- // this optimization would allow an invalid chain to be accepted.
- if (fScriptChecks) {
- // First check if script executions have been cached with the same
- // flags. Note that this assumes that the inputs provided are
- // correct (ie that the transaction hash which is in tx's prevouts
- // properly commits to the scriptPubKey in the inputs view of that
- // transaction).
- uint256 hashCacheEntry;
- // We only use the first 19 bytes of nonce to avoid a second SHA
- // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64)
- static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache");
- CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());
- AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks
- if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) {
- return true;
- }
-
- for (unsigned int i = 0; i < tx.vin.size(); i++) {
- const COutPoint &prevout = tx.vin[i].prevout;
- const Coin& coin = inputs.AccessCoin(prevout);
- assert(!coin.IsSpent());
-
- // We very carefully only pass in things to CScriptCheck which
- // are clearly committed to by tx' witness hash. This provides
- // a sanity check that our caching is not introducing consensus
- // failures through additional data in, eg, the coins being
- // spent being checked as a part of CScriptCheck.
-
- // Verify signature
- CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata);
- if (pvChecks) {
- pvChecks->push_back(CScriptCheck());
- check.swap(pvChecks->back());
- } else if (!check()) {
- if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
- // Check whether the failure was caused by a
- // non-mandatory script verification check, such as
- // non-standard DER encodings or non-null dummy
- // arguments; if so, ensure we return NOT_STANDARD
- // instead of CONSENSUS to avoid downstream users
- // splitting the network between upgraded and
- // non-upgraded nodes by banning CONSENSUS-failing
- // data providers.
- CScriptCheck check2(coin.out, tx, i,
- flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
- if (check2())
- return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
- }
- // MANDATORY flag failures correspond to
- // ValidationInvalidReason::CONSENSUS. Because CONSENSUS
- // failures are the most serious case of validation
- // failures, we may need to consider using
- // RECENT_CONSENSUS_CHANGE for any script failure that
- // could be due to non-upgraded nodes which we may want to
- // support, to avoid splitting the network (but this
- // depends on the details of how net_processing handles
- // such errors).
- return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
- }
- }
+ if (tx.IsCoinBase()) return true;
+
+ if (pvChecks) {
+ pvChecks->reserve(tx.vin.size());
+ }
+
+ // First check if script executions have been cached with the same
+ // flags. Note that this assumes that the inputs provided are
+ // correct (ie that the transaction hash which is in tx's prevouts
+ // properly commits to the scriptPubKey in the inputs view of that
+ // transaction).
+ uint256 hashCacheEntry;
+ // We only use the first 19 bytes of nonce to avoid a second SHA
+ // round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64)
+ static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache");
+ CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());
+ AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks
+ if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) {
+ return true;
+ }
- if (cacheFullScriptStore && !pvChecks) {
- // We executed all of the provided scripts, and were told to
- // cache the result. Do so now.
- scriptExecutionCache.insert(hashCacheEntry);
+ for (unsigned int i = 0; i < tx.vin.size(); i++) {
+ const COutPoint &prevout = tx.vin[i].prevout;
+ const Coin& coin = inputs.AccessCoin(prevout);
+ assert(!coin.IsSpent());
+
+ // We very carefully only pass in things to CScriptCheck which
+ // are clearly committed to by tx' witness hash. This provides
+ // a sanity check that our caching is not introducing consensus
+ // failures through additional data in, eg, the coins being
+ // spent being checked as a part of CScriptCheck.
+
+ // Verify signature
+ CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata);
+ if (pvChecks) {
+ pvChecks->push_back(CScriptCheck());
+ check.swap(pvChecks->back());
+ } else if (!check()) {
+ if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
+ // Check whether the failure was caused by a
+ // non-mandatory script verification check, such as
+ // non-standard DER encodings or non-null dummy
+ // arguments; if so, ensure we return NOT_STANDARD
+ // instead of CONSENSUS to avoid downstream users
+ // splitting the network between upgraded and
+ // non-upgraded nodes by banning CONSENSUS-failing
+ // data providers.
+ CScriptCheck check2(coin.out, tx, i,
+ flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
+ if (check2())
+ return state.Invalid(ValidationInvalidReason::TX_NOT_STANDARD, false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
+ // MANDATORY flag failures correspond to
+ // ValidationInvalidReason::CONSENSUS. Because CONSENSUS
+ // failures are the most serious case of validation
+ // failures, we may need to consider using
+ // RECENT_CONSENSUS_CHANGE for any script failure that
+ // could be due to non-upgraded nodes which we may want to
+ // support, to avoid splitting the network (but this
+ // depends on the details of how net_processing handles
+ // such errors).
+ return state.Invalid(ValidationInvalidReason::CONSENSUS, false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
}
}
+ if (cacheFullScriptStore && !pvChecks) {
+ // We executed all of the provided scripts, and were told to
+ // cache the result. Do so now.
+ scriptExecutionCache.insert(hashCacheEntry);
+ }
+
return true;
}
@@ -1769,6 +1802,11 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
pindexBestHeader->GetAncestor(pindex->nHeight) == pindex &&
pindexBestHeader->nChainWork >= nMinimumChainWork) {
// This block is a member of the assumed verified chain and an ancestor of the best header.
+ // Script verification is skipped when connecting blocks under the
+ // assumevalid block. Assuming the assumevalid block is valid this
+ // is safe because block merkle hashes are still computed and checked,
+ // Of course, if an assumed valid block is invalid due to false scriptSigs
+ // this optimization would allow an invalid chain to be accepted.
// The equivalent time check discourages hash power from extorting the network via DOS attack
// into accepting an invalid block through telling users they must manually set assumevalid.
// Requiring a software change or burying the invalid block, regardless of the setting, makes
@@ -1952,7 +1990,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
{
std::vector<CScriptCheck> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
- if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) {
+ if (fScriptChecks && !CheckInputs(tx, state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) {
if (state.GetReason() == ValidationInvalidReason::TX_NOT_STANDARD) {
// CheckInputs may return NOT_STANDARD for extra flags we passed,
// but we can't return that, as it's not defined for a block, so
@@ -2561,7 +2599,7 @@ bool CChainState::ActivateBestChainStep(CValidationState& state, const CChainPar
return true;
}
-static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) {
+static bool NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) {
bool fNotify = false;
bool fInitialBlockDownload = false;
static CBlockIndex* pindexHeaderOld = nullptr;
@@ -2580,6 +2618,7 @@ static void NotifyHeaderTip() LOCKS_EXCLUDED(cs_main) {
if (fNotify) {
uiInterface.NotifyHeaderTip(fInitialBlockDownload, pindexHeader);
}
+ return fNotify;
}
static void LimitValidationInterfaceQueue() LOCKS_EXCLUDED(cs_main) {
@@ -3400,7 +3439,11 @@ bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& headers, CValidatio
}
}
}
- NotifyHeaderTip();
+ if (NotifyHeaderTip()) {
+ 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);
+ }
+ }
return true;
}
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/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 7707d6233b..f52e4318c8 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -384,8 +384,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
}
- wtx.nIndex = txnIndex;
- wtx.hashBlock = merkleBlock.header.GetHash();
+ wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex);
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 30e767e489..b88aabd0fa 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -134,10 +134,10 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo
entry.pushKV("generated", true);
if (confirms > 0)
{
- entry.pushKV("blockhash", wtx.hashBlock.GetHex());
- entry.pushKV("blockindex", wtx.nIndex);
+ entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex());
+ entry.pushKV("blockindex", wtx.m_confirm.nIndex);
int64_t block_time;
- bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time);
+ bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time);
assert(found_block);
entry.pushKV("blocktime", block_time);
} else {
@@ -1649,6 +1649,7 @@ static UniValue gettransaction(const JSONRPCRequest& request)
{
{"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"},
},
RPCResult{
"{\n"
@@ -1684,11 +1685,13 @@ 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"
"}\n"
},
RPCExamples{
HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
+ + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
+ HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
},
}.Check(request);
@@ -1708,6 +1711,8 @@ static UniValue gettransaction(const JSONRPCRequest& request)
filter |= ISMINE_WATCH_ONLY;
}
+ bool decode_tx = request.params[2].isNull() ? false : request.params[2].get_bool();
+
UniValue entry(UniValue::VOBJ);
auto it = pwallet->mapWallet.find(hash);
if (it == pwallet->mapWallet.end()) {
@@ -1733,6 +1738,12 @@ static UniValue gettransaction(const JSONRPCRequest& request)
std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags());
entry.pushKV("hex", strHex);
+ if (decode_tx) {
+ UniValue decoded(UniValue::VOBJ);
+ TxToUniv(*wtx.tx, uint256(), decoded, false);
+ entry.pushKV("decoded", decoded);
+ }
+
return entry;
}
@@ -3269,7 +3280,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)
@@ -4175,7 +4189,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"} },
+ { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","decode"} },
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
{ "wallet", "getbalances", &getbalances, {} },
{ "wallet", "getwalletinfo", &getwalletinfo, {} },
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 8af05dea45..fc3be2b6ab 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -249,8 +249,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
LockAssertion lock(::cs_main);
LOCK(wallet.cs_wallet);
- wtx.hashBlock = ::ChainActive().Tip()->GetBlockHash();
- wtx.nIndex = 0;
+ wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0);
// Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0.
@@ -281,14 +280,19 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
}
CWalletTx wtx(&wallet, MakeTransactionRef(tx));
- if (block) {
- wtx.SetMerkleBranch(block->GetBlockHash(), 0);
- }
- {
- LOCK(cs_main);
+ LOCK(cs_main);
+ LOCK(wallet.cs_wallet);
+ // If transaction is already in map, to avoid inconsistencies, unconfirmation
+ // is needed before confirm again with different block.
+ std::map<uint256, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetHash());
+ if (it != wallet.mapWallet.end()) {
+ wtx.setUnconfirmed();
wallet.AddToWallet(wtx);
}
- LOCK(wallet.cs_wallet);
+ if (block) {
+ wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0);
+ }
+ wallet.AddToWallet(wtx);
return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart;
}
@@ -382,7 +386,7 @@ public:
LOCK(wallet->cs_wallet);
auto it = wallet->mapWallet.find(tx->GetHash());
BOOST_CHECK(it != wallet->mapWallet.end());
- it->second.SetMerkleBranch(::ChainActive().Tip()->GetBlockHash(), 1);
+ it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1);
return it->second;
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 03acf23508..7cf09d554b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -93,13 +93,14 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name)
static Mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
-static std::set<CWallet*> g_unloading_wallet_set;
+static std::set<std::string> g_unloading_wallet_set;
// Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet* wallet)
{
// Unregister and delete the wallet right after BlockUntilSyncedToCurrentChain
// so that it's in sync with the current chainstate.
+ const std::string name = wallet->GetName();
wallet->WalletLogPrintf("Releasing wallet\n");
wallet->BlockUntilSyncedToCurrentChain();
wallet->Flush();
@@ -108,7 +109,7 @@ static void ReleaseWallet(CWallet* wallet)
// Wallet is now released, notify UnloadWallet, if any.
{
LOCK(g_wallet_release_mutex);
- if (g_unloading_wallet_set.erase(wallet) == 0) {
+ if (g_unloading_wallet_set.erase(name) == 0) {
// UnloadWallet was not called for this wallet, all done.
return;
}
@@ -119,21 +120,21 @@ static void ReleaseWallet(CWallet* wallet)
void UnloadWallet(std::shared_ptr<CWallet>&& wallet)
{
// Mark wallet for unloading.
- CWallet* pwallet = wallet.get();
+ const std::string name = wallet->GetName();
{
LOCK(g_wallet_release_mutex);
- auto it = g_unloading_wallet_set.insert(pwallet);
+ auto it = g_unloading_wallet_set.insert(name);
assert(it.second);
}
// The wallet can be in use so it's not possible to explicitly unload here.
// Notify the unload intent so that all remaining shared pointers are
// released.
- pwallet->NotifyUnload();
+ wallet->NotifyUnload();
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
wallet.reset();
{
WAIT_LOCK(g_wallet_release_mutex, lock);
- while (g_unloading_wallet_set.count(pwallet) == 1) {
+ while (g_unloading_wallet_set.count(name) == 1) {
g_wallet_release_cv.wait(lock);
}
}
@@ -523,18 +524,9 @@ bool CWallet::LoadCScript(const CScript& redeemScript)
static bool ExtractPubKey(const CScript &dest, CPubKey& pubKeyOut)
{
- //TODO: Use Solver to extract this?
- CScript::const_iterator pc = dest.begin();
- opcodetype opcode;
- std::vector<unsigned char> vch;
- if (!dest.GetOp(pc, opcode, vch) || !CPubKey::ValidSize(vch))
- return false;
- pubKeyOut = CPubKey(vch);
- if (!pubKeyOut.IsFullyValid())
- return false;
- if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch))
- return false;
- return true;
+ std::vector<std::vector<unsigned char>> solutions;
+ return Solver(dest, solutions) == TX_PUBKEY &&
+ (pubKeyOut = CPubKey(solutions[0])).IsFullyValid();
}
bool CWallet::AddWatchOnlyInMem(const CScript &dest)
@@ -1118,22 +1110,14 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
bool fUpdated = false;
if (!fInsertedNew)
{
- // Merge
- if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock)
- {
- wtx.hashBlock = wtxIn.hashBlock;
- fUpdated = true;
- }
- // If no longer abandoned, update
- if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
- {
- wtx.hashBlock = wtxIn.hashBlock;
- fUpdated = true;
- }
- if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex))
- {
- wtx.nIndex = wtxIn.nIndex;
+ if (wtxIn.m_confirm.status != wtx.m_confirm.status) {
+ wtx.m_confirm.status = wtxIn.m_confirm.status;
+ wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex;
+ wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock;
fUpdated = true;
+ } else {
+ assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
+ assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
@@ -1180,8 +1164,19 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
return true;
}
-void CWallet::LoadToWallet(const CWalletTx& wtxIn)
+void CWallet::LoadToWallet(CWalletTx& wtxIn)
{
+ // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken.
+ auto locked_chain = LockChain();
+ // If tx hasn't been reorged out of chain while wallet being shutdown
+ // change tx status to UNCONFIRMED and reset hashBlock/nIndex.
+ if (!wtxIn.m_confirm.hashBlock.IsNull()) {
+ if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) {
+ wtxIn.setUnconfirmed();
+ wtxIn.m_confirm.hashBlock = uint256();
+ wtxIn.m_confirm.nIndex = 0;
+ }
+ }
uint256 hash = wtxIn.GetHash();
const auto& ins = mapWallet.emplace(hash, wtxIn);
CWalletTx& wtx = ins.first->second;
@@ -1194,14 +1189,14 @@ void CWallet::LoadToWallet(const CWalletTx& wtxIn)
auto it = mapWallet.find(txin.prevout.hash);
if (it != mapWallet.end()) {
CWalletTx& prevtx = it->second;
- if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
- MarkConflicted(prevtx.hashBlock, wtx.GetHash());
+ if (prevtx.isConflicted()) {
+ MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash());
}
}
}
}
-bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool fUpdate)
+bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate)
{
const CTransaction& tx = *ptx;
{
@@ -1248,9 +1243,9 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const uint256
CWalletTx wtx(this, ptx);
- // Get merkle branch if transaction was found in a block
- if (!block_hash.IsNull())
- wtx.SetMerkleBranch(block_hash, posInBlock);
+ // Block disconnection override an abandoned tx as unconfirmed
+ // which means user may have to call abandontransaction again
+ wtx.SetConf(status, block_hash, posInBlock);
return AddToWallet(wtx, false);
}
@@ -1310,7 +1305,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can be in mempool
assert(!wtx.InMempool());
- wtx.nIndex = -1;
+ wtx.m_confirm.nIndex = 0;
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
@@ -1364,8 +1359,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
- wtx.nIndex = -1;
- wtx.hashBlock = hashBlock;
+ wtx.m_confirm.nIndex = 0;
+ wtx.m_confirm.hashBlock = hashBlock;
+ wtx.setConflicted();
wtx.MarkDirty();
batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
@@ -1383,8 +1379,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
}
-void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_hash, int posInBlock, bool update_tx) {
- if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx))
+void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx)
+{
+ if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
@@ -1396,7 +1393,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, const uint256& block_h
void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
+ SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
auto it = mapWallet.find(ptx->GetHash());
if (it != mapWallet.end()) {
@@ -1416,22 +1413,14 @@ void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransaction
const uint256& block_hash = block.GetHash();
auto locked_chain = chain().lock();
LOCK(cs_wallet);
- // TODO: Temporarily ensure that mempool removals are notified before
- // connected transactions. This shouldn't matter, but the abandoned
- // state of transactions in our wallet is currently cleared when we
- // receive another notification and there is a race condition where
- // notification of a connected conflict might cause an outside process
- // to abandon a transaction and then have it inadvertently cleared by
- // the notification that the conflicted transaction was evicted.
- for (const CTransactionRef& ptx : vtxConflicted) {
- SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
- TransactionRemovedFromMempool(ptx);
- }
for (size_t i = 0; i < block.vtx.size(); i++) {
- SyncTransaction(block.vtx[i], block_hash, i);
+ SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i);
TransactionRemovedFromMempool(block.vtx[i]);
}
+ for (const CTransactionRef& ptx : vtxConflicted) {
+ TransactionRemovedFromMempool(ptx);
+ }
m_last_block_processed = block_hash;
}
@@ -1440,8 +1429,12 @@ void CWallet::BlockDisconnected(const CBlock& block) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
+ // At block disconnection, this will change an abandoned transaction to
+ // be unconfirmed, whether or not the transaction is added back to the mempool.
+ // User may have to call abandontransaction again. It may be addressed in the
+ // future with a stickier abandoned state or even removing abandontransaction call.
for (const CTransactionRef& ptx : block.vtx) {
- SyncTransaction(ptx, {} /* block hash */, 0 /* position in block */);
+ SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */);
}
}
@@ -2078,7 +2071,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate);
+ SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate);
}
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
@@ -3340,6 +3333,11 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
+ // Even if we don't use this lock in this function, we want to preserve
+ // lock order in LoadToWallet if query of chain state is needed to know
+ // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation
+ // status may be not reliable.
+ auto locked_chain = LockChain();
LOCK(cs_wallet);
fFirstRunRet = false;
@@ -4050,7 +4048,7 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
for (const auto& entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
- if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) {
+ if (Optional<int> height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// iterate over all their outputs
@@ -4093,9 +4091,9 @@ void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<C
unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
{
unsigned int nTimeSmart = wtx.nTimeReceived;
- if (!wtx.hashUnset()) {
+ if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
int64_t blocktime;
- if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) {
+ if (chain().findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &blocktime)) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
@@ -4123,7 +4121,7 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
- WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.hashBlock.ToString());
+ WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetHash().ToString(), wtx.m_confirm.hashBlock.ToString());
}
}
return nTimeSmart;
@@ -4241,6 +4239,11 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b
// Recover readable keypairs:
CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy());
std::string backup_filename;
+ // Even if we don't use this lock in this function, we want to preserve
+ // lock order in LoadToWallet if query of chain state is needed to know
+ // tx status. If lock can't be taken, tx confirmation status may be not
+ // reliable.
+ auto locked_chain = dummyWallet.LockChain();
if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
return false;
}
@@ -4251,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;
@@ -4395,23 +4398,23 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
}
if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) {
- chain.initError(strprintf("Unknown address type '%s'", gArgs.GetArg("-addresstype", "")));
+ chain.initError(strprintf(_("Unknown address type '%s'").translated, gArgs.GetArg("-addresstype", "")));
return nullptr;
}
if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) {
- chain.initError(strprintf("Unknown change type '%s'", gArgs.GetArg("-changetype", "")));
+ chain.initError(strprintf(_("Unknown change type '%s'").translated, gArgs.GetArg("-changetype", "")));
return nullptr;
}
if (gArgs.IsArgSet("-mintxfee")) {
CAmount n = 0;
if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) {
- chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")));
+ chain.initError(AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")).translated);
return nullptr;
}
if (n > HIGH_TX_FEE_PER_KB) {
- chain.initWarning(AmountHighWarn("-mintxfee") + " " +
+ chain.initWarning(AmountHighWarn("-mintxfee").translated + " " +
_("This is the minimum transaction fee you pay on every transaction.").translated);
}
walletInstance->m_min_fee = CFeeRate(n);
@@ -4425,7 +4428,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
- chain.initWarning(AmountHighWarn("-fallbackfee") + " " +
+ chain.initWarning(AmountHighWarn("-fallbackfee").translated + " " +
_("This is the transaction fee you may pay when fee estimates are not available.").translated);
}
walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
@@ -4438,7 +4441,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
- chain.initWarning(AmountHighWarn("-discardfee") + " " +
+ chain.initWarning(AmountHighWarn("-discardfee").translated + " " +
_("This is the transaction fee you may discard if change is smaller than dust at this level").translated);
}
walletInstance->m_discard_rate = CFeeRate(nFeePerK);
@@ -4446,11 +4449,11 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
if (gArgs.IsArgSet("-paytxfee")) {
CAmount nFeePerK = 0;
if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
- chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")));
+ chain.initError(AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")).translated);
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
- chain.initWarning(AmountHighWarn("-paytxfee") + " " +
+ chain.initWarning(AmountHighWarn("-paytxfee").translated + " " +
_("This is the transaction fee you will pay if you send a transaction.").translated);
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
@@ -4465,7 +4468,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
{
CAmount nMaxFee = 0;
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
- chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
+ chain.initError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")).translated);
return nullptr;
}
if (nMaxFee > HIGH_MAX_TX_FEE) {
@@ -4479,9 +4482,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
walletInstance->m_default_max_tx_fee = nMaxFee;
}
- if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB)
- chain.initWarning(AmountHighWarn("-minrelaytxfee") + " " +
+ if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
+ chain.initWarning(AmountHighWarn("-minrelaytxfee").translated + " " +
_("The wallet will avoid paying less than the minimum relay fee.").translated);
+ }
walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET);
walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
@@ -4634,21 +4638,23 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
m_pre_split = false;
}
-void CWalletTx::SetMerkleBranch(const uint256& block_hash, int posInBlock)
+void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock)
{
+ // Update tx status
+ m_confirm.status = status;
+
// Update the tx's hashBlock
- hashBlock = block_hash;
+ m_confirm.hashBlock = block_hash;
// set the position of the transaction in the block
- nIndex = posInBlock;
+ m_confirm.nIndex = posInBlock;
}
int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const
{
- if (hashUnset())
- return 0;
+ if (isUnconfirmed() || isAbandoned()) return 0;
- return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1);
+ return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1);
}
int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cf388ad827..3428e8e001 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -396,7 +396,9 @@ class CWalletTx
private:
const CWallet* pwallet;
- /** Constant used in hashBlock to indicate tx has been abandoned */
+ /** Constant used in hashBlock to indicate tx has been abandoned, only used at
+ * serialization/deserialization to avoid ambiguity with conflicted.
+ */
static const uint256 ABANDON_HASH;
public:
@@ -444,7 +446,7 @@ public:
* on this bitcoin node, and set to 0 for transactions that were created
* externally and came in through the network or sendrawtransaction RPC.
*/
- char fFromMe;
+ bool fFromMe;
int64_t nOrderPos; //!< position in ordered transaction list
std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered;
@@ -457,9 +459,7 @@ public:
mutable CAmount nChangeCached;
CWalletTx(const CWallet* pwalletIn, CTransactionRef arg)
- : tx(std::move(arg)),
- hashBlock(uint256()),
- nIndex(-1)
+ : tx(std::move(arg))
{
Init(pwalletIn);
}
@@ -477,16 +477,37 @@ public:
fInMempool = false;
nChangeCached = 0;
nOrderPos = -1;
+ m_confirm = Confirmation{};
}
CTransactionRef tx;
- uint256 hashBlock;
- /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
- * block in the chain we know this or any in-wallet dependency conflicts
- * with. Older clients interpret nIndex == -1 as unconfirmed for backward
- * compatibility.
+
+ /* New transactions start as UNCONFIRMED. At BlockConnected,
+ * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected,
+ * they roll back to UNCONFIRMED. If we detect a conflicting transaction at
+ * block connection, we update conflicted tx and its dependencies as CONFLICTED.
+ * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED
+ * by using the abandontransaction call. This last status may be override by a CONFLICTED
+ * or CONFIRMED transition.
+ */
+ enum Status {
+ UNCONFIRMED,
+ CONFIRMED,
+ CONFLICTED,
+ ABANDONED
+ };
+
+ /* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed.
+ * This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state
+ * where they instead point to block hash and index of the deepest conflicting tx.
*/
- int nIndex;
+ struct Confirmation {
+ Status status = UNCONFIRMED;
+ uint256 hashBlock = uint256();
+ int nIndex = 0;
+ };
+
+ Confirmation m_confirm;
template<typename Stream>
void Serialize(Stream& s) const
@@ -501,8 +522,10 @@ public:
std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<char> dummy_vector2; //!< Used to be vtxPrev
- char dummy_char = false; //!< Used to be fSpent
- s << tx << hashBlock << dummy_vector1 << nIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_char;
+ bool dummy_bool = false; //!< Used to be fSpent
+ uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock;
+ int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex;
+ s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
}
template<typename Stream>
@@ -512,8 +535,26 @@ public:
std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch
std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev
- char dummy_char; //! Used to be fSpent
- s >> tx >> hashBlock >> dummy_vector1 >> nIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_char;
+ bool dummy_bool; //! Used to be fSpent
+ int serializedIndex;
+ s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool;
+
+ /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to
+ * the earliest block in the chain we know this or any in-wallet ancestor conflicts
+ * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned.
+ * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or
+ * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward
+ * compatibility (pre-commit 9ac63d6).
+ */
+ if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
+ m_confirm.hashBlock = uint256();
+ setAbandoned();
+ } else if (serializedIndex == -1) {
+ setConflicted();
+ } else if (!m_confirm.hashBlock.IsNull()) {
+ m_confirm.nIndex = serializedIndex;
+ setConfirmed();
+ }
ReadOrderPos(nOrderPos, mapValue);
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
@@ -590,7 +631,7 @@ public:
// in place.
std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
- void SetMerkleBranch(const uint256& block_hash, int posInBlock);
+ void SetConf(Status status, const uint256& block_hash, int posInBlock);
/**
* Return depth of transaction in blockchain:
@@ -607,10 +648,18 @@ public:
* >0 : is a coinbase transaction which matures in this many blocks
*/
int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const;
- bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
- bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
- void setAbandoned() { hashBlock = ABANDON_HASH; }
-
+ bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
+ void setAbandoned()
+ {
+ m_confirm.status = CWalletTx::ABANDONED;
+ m_confirm.hashBlock = uint256();
+ m_confirm.nIndex = 0;
+ }
+ bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; }
+ void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
+ bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; }
+ void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
+ void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); }
bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const;
@@ -750,7 +799,7 @@ private:
* Abandoned state should probably be more carefully tracked via different
* posInBlock signals or by checking mempool presence when necessary.
*/
- bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
@@ -762,7 +811,7 @@ private:
/* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
* Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */
- void SyncTransaction(const CTransactionRef& tx, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
@@ -897,6 +946,9 @@ public:
bool IsLocked() const;
bool Lock();
+ /** Interface to assert chain access and if successful lock it */
+ std::unique_ptr<interfaces::Chain::Lock> LockChain() { return m_chain ? m_chain->lock() : nullptr; }
+
std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
typedef std::multimap<int64_t, CWalletTx*> TxItems;
@@ -1042,7 +1094,7 @@ public:
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
- void LoadToWallet(const CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void TransactionAddedToMempool(const CTransactionRef& tx) override;
void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override;
void BlockDisconnected(const CBlock& block) override;
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 {