aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.test_fuzz.include1
-rw-r--r--src/bench/coin_selection.cpp14
-rw-r--r--src/bitcoin-cli.cpp2
-rw-r--r--src/compat/glibc_compat.cpp6
-rw-r--r--src/init.cpp2
-rw-r--r--src/net.cpp18
-rw-r--r--src/net.h11
-rw-r--r--src/net_processing.cpp31
-rw-r--r--src/qt/README.md121
-rw-r--r--src/qt/addressbookpage.cpp2
-rw-r--r--src/qt/askpassphrasedialog.cpp2
-rw-r--r--src/qt/bitcoin.cpp8
-rw-r--r--src/qt/coincontroldialog.cpp2
-rw-r--r--src/qt/createwalletdialog.cpp4
-rw-r--r--src/qt/editaddressdialog.cpp2
-rw-r--r--src/qt/forms/debugwindow.ui2
-rw-r--r--src/qt/guiutil.cpp20
-rw-r--r--src/qt/guiutil.h5
-rw-r--r--src/qt/intro.cpp2
-rw-r--r--src/qt/openuridialog.cpp2
-rw-r--r--src/qt/optionsdialog.cpp2
-rw-r--r--src/qt/psbtoperationsdialog.cpp2
-rw-r--r--src/qt/receivecoinsdialog.cpp3
-rw-r--r--src/qt/receiverequestdialog.cpp2
-rw-r--r--src/qt/rpcconsole.cpp20
-rw-r--r--src/qt/sendcoinsdialog.cpp2
-rw-r--r--src/qt/signverifymessagedialog.cpp2
-rw-r--r--src/qt/test/rpcnestedtests.cpp2
-rw-r--r--src/qt/transactiondescdialog.cpp2
-rw-r--r--src/qt/utilitydialog.cpp2
-rw-r--r--src/rpc/blockchain.cpp66
-rw-r--r--src/rpc/client.cpp11
-rw-r--r--src/rpc/mining.cpp28
-rw-r--r--src/rpc/misc.cpp30
-rw-r--r--src/rpc/net.cpp36
-rw-r--r--src/rpc/protocol.h1
-rw-r--r--src/rpc/rawtransaction.cpp47
-rw-r--r--src/rpc/server.cpp31
-rw-r--r--src/rpc/server.h8
-rw-r--r--src/rpc/util.cpp18
-rw-r--r--src/rpc/util.h2
-rw-r--r--src/test/fuzz/process_message.cpp6
-rw-r--r--src/test/fuzz/process_messages.cpp5
-rw-r--r--src/test/fuzz/util.cpp25
-rw-r--r--src/test/fuzz/util.h23
-rw-r--r--src/validation.cpp6
-rw-r--r--src/wallet/coinselection.cpp66
-rw-r--r--src/wallet/coinselection.h28
-rw-r--r--src/wallet/rpcdump.cpp69
-rw-r--r--src/wallet/rpcwallet.cpp139
-rw-r--r--src/wallet/test/coinselector_tests.cpp102
-rw-r--r--src/wallet/wallet.cpp165
-rw-r--r--src/wallet/wallet.h16
-rw-r--r--src/zmq/zmqrpc.cpp6
54 files changed, 733 insertions, 497 deletions
diff --git a/src/Makefile.test_fuzz.include b/src/Makefile.test_fuzz.include
index 4e858979fe..75fe68fcd1 100644
--- a/src/Makefile.test_fuzz.include
+++ b/src/Makefile.test_fuzz.include
@@ -16,6 +16,7 @@ libtest_fuzz_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAG
libtest_fuzz_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libtest_fuzz_a_SOURCES = \
test/fuzz/fuzz.cpp \
+ test/fuzz/util.cpp \
$(TEST_FUZZ_H)
LIBTEST_FUZZ += $(LIBBITCOIN_SERVER)
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index e50c4476bb..3abfbfd784 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -42,20 +42,19 @@ static void CoinSelection(benchmark::Bench& bench)
}
addCoin(3 * COIN, wallet, wtxs);
- // Create groups
- std::vector<OutputGroup> groups;
+ // Create coins
+ std::vector<COutput> coins;
for (const auto& wtx : wtxs) {
- COutput output(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
- groups.emplace_back(output.GetInputCoin(), 6, false, 0, 0);
+ coins.emplace_back(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
}
const CoinEligibilityFilter filter_standard(1, 6, 0);
- const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0);
+ const CoinSelectionParams coin_selection_params(true, 34, 148, CFeeRate(0), 0, false);
bench.run([&] {
std::set<CInputCoin> setCoinsRet;
CAmount nValueRet;
bool bnb_used;
- bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
+ bool success = wallet.SelectCoinsMinConf(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params, bnb_used);
assert(success);
assert(nValueRet == 1003 * COIN);
assert(setCoinsRet.size() == 2);
@@ -75,7 +74,8 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx)));
- set.emplace_back(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0);
+ set.emplace_back();
+ set.back().Insert(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false);
wtxn.emplace_back(std::move(wtx));
}
// Copied from src/wallet/test/coinselector_tests.cpp
diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp
index 94043a6b45..6a0d44a183 100644
--- a/src/bitcoin-cli.cpp
+++ b/src/bitcoin-cli.cpp
@@ -57,7 +57,7 @@ static void SetupCliArgs(ArgsManager& argsman)
argsman.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-conf=<file>", strprintf("Specify configuration file. Relative paths will be prefixed by datadir location. (default: %s)", BITCOIN_CONF_FILENAME), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-datadir=<dir>", "Specify data directory", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
- argsman.AddArg("-generate", strprintf("Generate blocks immediately, equivalent to RPC generatenewaddress followed by RPC generatetoaddress. Optional positional integer arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to RPC generatetoaddress nblocks and maxtries arguments. Example: bitcoin-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
+ argsman.AddArg("-generate", strprintf("Generate blocks immediately, equivalent to RPC getnewaddress followed by RPC generatetoaddress. Optional positional integer arguments are number of blocks to generate (default: %s) and maximum iterations to try (default: %s), equivalent to RPC generatetoaddress nblocks and maxtries arguments. Example: bitcoin-cli -generate 4 1000", DEFAULT_NBLOCKS, DEFAULT_MAX_TRIES), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-getinfo", "Get general information from the remote server. Note that unlike server-side RPC calls, the results of -getinfo is the result of multiple non-atomic requests. Some entries in the result may represent results from different states (e.g. wallet balance may be as of a different block from the chain state reported)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-netinfo", "Get network peer connection information from the remote server. An optional integer argument from 0 to 4 can be passed for different peers listings (default: 0). Pass \"help\" for detailed help documentation.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
diff --git a/src/compat/glibc_compat.cpp b/src/compat/glibc_compat.cpp
index 6d7a293f9b..8a51f310f7 100644
--- a/src/compat/glibc_compat.cpp
+++ b/src/compat/glibc_compat.cpp
@@ -54,6 +54,12 @@ __asm(".symver log2f_old,log2f@GLIBC_2.2.5");
__asm(".symver log2f_old,log2f@GLIBC_2.4");
#elif defined(__aarch64__)
__asm(".symver log2f_old,log2f@GLIBC_2.17");
+#elif defined(__powerpc64__)
+# ifdef WORDS_BIGENDIAN
+__asm(".symver log2f_old,log2f@GLIBC_2.3");
+# else
+__asm(".symver log2f_old,log2f@GLIBC_2.17");
+# endif
#elif defined(__riscv)
__asm(".symver log2f_old,log2f@GLIBC_2.27");
#endif
diff --git a/src/init.cpp b/src/init.cpp
index 1369023c2d..319d6a52f4 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -448,7 +448,7 @@ void SetupServerArgs(NodeContext& node)
argsman.AddArg("-maxtimeadjustment", strprintf("Maximum allowed median peer time offset adjustment. Local perspective of time may be influenced by peers forward or backward by this amount. (default: %u seconds)", DEFAULT_MAX_TIME_ADJUSTMENT), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-maxuploadtarget=<n>", strprintf("Tries to keep outbound traffic under the given target (in MiB per 24h). Limit does not apply to peers with 'download' permission. 0 = no limit (default: %d)", DEFAULT_MAX_UPLOAD_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-onion=<ip:port>", "Use separate SOCKS5 proxy to reach peers via Tor onion services, set -noonion to disable (default: -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
- argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
+ argsman.AddArg("-onlynet=<net>", "Make outgoing connections only through network <net> (ipv4, ipv6 or onion). Incoming connections are not affected by this option. This option can be specified multiple times to allow multiple networks. Warning: if it is used with ipv4 or ipv6 but not onion and the -onion or -proxy option is set, then outbound onion connections will still be made; use -noonion or -onion=0 to disable outbound onion connections in this case.", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerbloomfilters", strprintf("Support filtering of blocks and transaction with bloom filters (default: %u)", DEFAULT_PEERBLOOMFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-peerblockfilters", strprintf("Serve compact block filters to peers per BIP 157 (default: %u)", DEFAULT_PEERBLOCKFILTERS), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
diff --git a/src/net.cpp b/src/net.cpp
index 4759287c36..76bf7effa4 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -815,7 +815,7 @@ size_t CConnman::SocketSendData(CNode& node) const
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) {
- LogPrintf("socket send error %s\n", NetworkErrorString(nErr));
+ LogPrint(BCLog::NET, "socket send error for peer=%d: %s\n", node.GetId(), NetworkErrorString(nErr));
node.CloseSocketDisconnect();
}
}
@@ -1004,6 +1004,7 @@ bool CConnman::AttemptToEvictConnection()
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
if (pnode->GetId() == *node_id_to_evict) {
+ LogPrint(BCLog::NET, "selected %s connection for eviction peer=%d; disconnecting\n", pnode->ConnectionTypeAsString(), pnode->GetId());
pnode->fDisconnect = true;
return true;
}
@@ -1052,7 +1053,7 @@ void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
}
if (!fNetworkActive) {
- LogPrintf("connection from %s dropped: not accepting new connections\n", addr.ToString());
+ LogPrint(BCLog::NET, "connection from %s dropped: not accepting new connections\n", addr.ToString());
CloseSocket(hSocket);
return;
}
@@ -1230,17 +1231,17 @@ bool CConnman::InactivityCheck(const CNode& node) const
}
if (node.nLastRecv == 0 || node.nLastSend == 0) {
- LogPrint(BCLog::NET, "socket no message in first %i seconds, %d %d from %d\n", m_peer_connect_timeout, node.nLastRecv != 0, node.nLastSend != 0, node.GetId());
+ LogPrint(BCLog::NET, "socket no message in first %i seconds, %d %d peer=%d\n", m_peer_connect_timeout, node.nLastRecv != 0, node.nLastSend != 0, node.GetId());
return true;
}
if (now > node.nLastSend + TIMEOUT_INTERVAL) {
- LogPrintf("socket sending timeout: %is\n", now - node.nLastSend);
+ LogPrint(BCLog::NET, "socket sending timeout: %is peer=%d\n", now - node.nLastSend, node.GetId());
return true;
}
if (now > node.nLastRecv + TIMEOUT_INTERVAL) {
- LogPrintf("socket receive timeout: %is\n", now - node.nLastRecv);
+ LogPrint(BCLog::NET, "socket receive timeout: %is peer=%d\n", now - node.nLastRecv, node.GetId());
return true;
}
@@ -1248,12 +1249,12 @@ bool CConnman::InactivityCheck(const CNode& node) const
// We use mockable time for ping timeouts. This means that setmocktime
// may cause pings to time out for peers that have been connected for
// longer than m_peer_connect_timeout.
- LogPrintf("ping timeout: %fs\n", 0.000001 * count_microseconds(GetTime<std::chrono::microseconds>() - node.m_ping_start.load()));
+ LogPrint(BCLog::NET, "ping timeout: %fs peer=%d\n", 0.000001 * count_microseconds(GetTime<std::chrono::microseconds>() - node.m_ping_start.load()), node.GetId());
return true;
}
if (!node.fSuccessfullyConnected) {
- LogPrint(BCLog::NET, "version handshake timeout from %d\n", node.GetId());
+ LogPrint(BCLog::NET, "version handshake timeout peer=%d\n", node.GetId());
return true;
}
@@ -2693,6 +2694,7 @@ bool CConnman::DisconnectNode(const std::string& strNode)
{
LOCK(cs_vNodes);
if (CNode* pnode = FindNode(strNode)) {
+ LogPrint(BCLog::NET, "disconnect by address%s matched peer=%d; disconnecting\n", (fLogIPs ? strprintf("=%s", strNode) : ""), pnode->GetId());
pnode->fDisconnect = true;
return true;
}
@@ -2705,6 +2707,7 @@ bool CConnman::DisconnectNode(const CSubNet& subnet)
LOCK(cs_vNodes);
for (CNode* pnode : vNodes) {
if (subnet.Match(pnode->addr)) {
+ LogPrint(BCLog::NET, "disconnect by subnet%s matched peer=%d; disconnecting\n", (fLogIPs ? strprintf("=%s", subnet.ToString()) : ""), pnode->GetId());
pnode->fDisconnect = true;
disconnected = true;
}
@@ -2722,6 +2725,7 @@ bool CConnman::DisconnectNode(NodeId id)
LOCK(cs_vNodes);
for(CNode* pnode : vNodes) {
if (id == pnode->GetId()) {
+ LogPrint(BCLog::NET, "disconnect by id peer=%d; disconnecting\n", pnode->GetId());
pnode->fDisconnect = true;
return true;
}
diff --git a/src/net.h b/src/net.h
index 3a1b55d054..86fcee512a 100644
--- a/src/net.h
+++ b/src/net.h
@@ -111,7 +111,8 @@ struct CSerializedNetMsg
* connection. Aside from INBOUND, all types are initiated by us.
*
* If adding or removing types, please update CONNECTION_TYPE_DOC in
- * src/rpc/net.cpp. */
+ * src/rpc/net.cpp and src/qt/rpcconsole.cpp, as well as the descriptions in
+ * src/qt/guiutil.cpp and src/bitcoin-cli.cpp::NetinfoRequestHandler. */
enum class ConnectionType {
/**
* Inbound connections are those initiated by a peer. This is the only
@@ -122,7 +123,7 @@ enum class ConnectionType {
/**
* These are the default connections that we use to connect with the
- * network. There is no restriction on what is relayed- by default we relay
+ * network. There is no restriction on what is relayed; by default we relay
* blocks, addresses & transactions. We automatically attempt to open
* MAX_OUTBOUND_FULL_RELAY_CONNECTIONS using addresses from our AddrMan.
*/
@@ -130,8 +131,8 @@ enum class ConnectionType {
/**
- * We open manual connections to addresses that users explicitly inputted
- * via the addnode RPC, or the -connect command line argument. Even if a
+ * We open manual connections to addresses that users explicitly requested
+ * via the addnode RPC or the -addnode/-connect configuration options. Even if a
* manual connection is misbehaving, we do not automatically disconnect or
* add it to our discouragement filter.
*/
@@ -150,7 +151,7 @@ enum class ConnectionType {
* although in our codebase feeler connections encompass test-before-evict as well.
* We make these connections approximately every FEELER_INTERVAL:
* first we resolve previously found collisions if they exist (test-before-evict),
- * otherwise connect to a node from the new table.
+ * otherwise we connect to a node from the new table.
*/
FEELER,
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 087f35251a..b68453759a 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -880,13 +880,14 @@ void PeerManagerImpl::PushNodeVersion(CNode& pnode, int64_t nTime)
CAddress(CService(), addr.nServices);
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
+ const bool tx_relay = !m_ignore_incoming_txs && pnode.m_tx_relay != nullptr;
m_connman.PushMessage(&pnode, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERSION, PROTOCOL_VERSION, (uint64_t)nLocalNodeServices, nTime, addrYou, addrMe,
- nonce, strSubVersion, nNodeStartingHeight, !m_ignore_incoming_txs && pnode.m_tx_relay != nullptr));
+ nonce, strSubVersion, nNodeStartingHeight, tx_relay));
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);
+ LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, them=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), addrYou.ToString(), tx_relay, nodeid);
} else {
- LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), nodeid);
+ LogPrint(BCLog::NET, "send version message: version %d, blocks=%d, us=%s, txrelay=%d, peer=%d\n", PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), tx_relay, nodeid);
}
}
@@ -2642,9 +2643,9 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (fLogIPs)
remoteAddr = ", peeraddr=" + pfrom.addr.ToString();
- LogPrint(BCLog::NET, "receive version message: %s: version %d, blocks=%d, us=%s, peer=%d%s\n",
+ LogPrint(BCLog::NET, "receive version message: %s: version %d, blocks=%d, us=%s, txrelay=%d, peer=%d%s\n",
cleanSubVer, pfrom.nVersion,
- peer->m_starting_height, addrMe.ToString(), pfrom.GetId(),
+ peer->m_starting_height, addrMe.ToString(), fRelay, pfrom.GetId(),
remoteAddr);
int64_t nTimeOffset = nTime - GetTime();
@@ -2659,6 +2660,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
// Feeler connections exist only to verify if address is online.
if (pfrom.IsFeelerConn()) {
+ LogPrint(BCLog::NET, "feeler connection completed peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
}
return;
@@ -2674,7 +2676,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
const CNetMsgMaker msgMaker(pfrom.GetCommonVersion());
if (msg_type == NetMsgType::VERACK) {
- if (pfrom.fSuccessfullyConnected) return;
+ if (pfrom.fSuccessfullyConnected) {
+ LogPrint(BCLog::NET, "ignoring redundant verack message from peer=%d\n", pfrom.GetId());
+ return;
+ }
if (!pfrom.IsInboundConn()) {
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n",
@@ -2746,6 +2751,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (pfrom.fSuccessfullyConnected) {
// Disconnect peers that send wtxidrelay message after VERACK; this
// must be negotiated between VERSION and VERACK.
+ LogPrint(BCLog::NET, "wtxidrelay received after verack from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
@@ -2754,7 +2760,11 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (!State(pfrom.GetId())->m_wtxid_relay) {
State(pfrom.GetId())->m_wtxid_relay = true;
g_wtxid_relay_peers++;
+ } else {
+ LogPrint(BCLog::NET, "ignoring duplicate wtxidrelay from peer=%d\n", pfrom.GetId());
}
+ } else {
+ LogPrint(BCLog::NET, "ignoring wtxidrelay due to old common version=%d from peer=%d\n", pfrom.GetCommonVersion(), pfrom.GetId());
}
return;
}
@@ -2763,6 +2773,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (pfrom.fSuccessfullyConnected) {
// Disconnect peers that send SENDADDRV2 message after VERACK; this
// must be negotiated between VERSION and VERACK.
+ LogPrint(BCLog::NET, "sendaddrv2 received after verack from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
@@ -2789,6 +2800,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
s >> vAddr;
if (!pfrom.RelayAddrsWithConn()) {
+ LogPrint(BCLog::NET, "ignoring %s message from %s peer=%d\n", msg_type, pfrom.ConnectionTypeAsString(), pfrom.GetId());
return;
}
if (vAddr.size() > MAX_ADDR_TO_SEND)
@@ -2832,8 +2844,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
m_connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60);
if (vAddr.size() < 1000)
pfrom.fGetAddr = false;
- if (pfrom.IsAddrFetchConn())
+ if (pfrom.IsAddrFetchConn()) {
+ LogPrint(BCLog::NET, "addrfetch connection completed peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
+ }
return;
}
@@ -3840,6 +3854,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (msg_type == NetMsgType::FILTERLOAD) {
if (!(pfrom.GetLocalServices() & NODE_BLOOM)) {
+ LogPrint(BCLog::NET, "filterload received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
@@ -3862,6 +3877,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (msg_type == NetMsgType::FILTERADD) {
if (!(pfrom.GetLocalServices() & NODE_BLOOM)) {
+ LogPrint(BCLog::NET, "filteradd received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
@@ -3889,6 +3905,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
if (msg_type == NetMsgType::FILTERCLEAR) {
if (!(pfrom.GetLocalServices() & NODE_BLOOM)) {
+ LogPrint(BCLog::NET, "filterclear received despite not offering bloom services from peer=%d; disconnecting\n", pfrom.GetId());
pfrom.fDisconnect = true;
return;
}
diff --git a/src/qt/README.md b/src/qt/README.md
index 30c68db15b..20c712c98d 100644
--- a/src/qt/README.md
+++ b/src/qt/README.md
@@ -1,10 +1,12 @@
-This directory contains the BitcoinQT graphical user interface (GUI). It uses the cross-platform framework [Qt](https://www1.qt.io/developers/).
+This directory contains the source code for the Bitcoin Core graphical user interface (GUI). It uses the [Qt](https://www1.qt.io/developers/) cross-platform framework.
The current precise version for Qt 5 is specified in [qt.mk](/depends/packages/qt.mk).
## Compile and run
-See build instructions ([macOS](/doc/build-osx.md), [Windows](/doc/build-windows.md), [Unix](/doc/build-unix.md), etc).
+See build instructions: [Unix](/doc/build-unix.md), [macOS](/doc/build-osx.md), [Windows](/doc/build-windows.md), [FreeBSD](/doc/build-freebsd.md), [NetBSD](/doc/build-netbsd.md), [OpenBSD](/doc/build-openbsd.md)
+
+When following your systems build instructions, make sure to install the `Qt` dependencies.
To run:
@@ -12,84 +14,111 @@ To run:
./src/qt/bitcoin-qt
```
-## Files and directories
-
-### forms
+## Files and Directories
-Contains [Designer UI](https://doc.qt.io/qt-5.9/designer-using-a-ui-file.html) files. They are created with [Qt Creator](#using-qt-creator-as-ide), but can be edited using any text editor.
+#### forms/
-### locale
+- A directory that contains [Designer UI](https://doc.qt.io/qt-5.9/designer-using-a-ui-file.html) files. These files specify the characteristics of form elements in XML. Qt UI files can be edited with [Qt Creator](#using-qt-creator-as-ide) or using any text editor.
-Contains translations. They are periodically updated. The process is described [here](/doc/translation_process.md).
+#### locale/
-### res
+- Contains translations. They are periodically updated and an effort is made to support as many languages as possible. The process of contributing translations is described in [doc/translation_process.md](/doc/translation_process.md).
-Resources such as the icon.
+#### res/
-### test
+ - Contains graphical resources used to enhance the UI experience.
-Tests.
+#### test/
-### bitcoingui.(h/cpp)
+- Functional tests used to ensure proper functionality of the GUI. Significant changes to the GUI code normally require new or updated tests.
-Represents the main window of the Bitcoin UI.
+#### bitcoingui.(h/cpp)
-### \*model.(h/cpp)
+- Represents the main window of the Bitcoin UI.
-The model. When it has a corresponding controller, it generally inherits from [QAbstractTableModel](https://doc.qt.io/qt-5/qabstracttablemodel.html). Models that are used by controllers as helpers inherit from other Qt classes like [QValidator](https://doc.qt.io/qt-5/qvalidator.html).
+#### \*model.(h/cpp)
-ClientModel is used by the main application `bitcoingui` and several models like `peertablemodel`.
+- The model. When it has a corresponding controller, it generally inherits from [QAbstractTableModel](https://doc.qt.io/qt-5/qabstracttablemodel.html). Models that are used by controllers as helpers inherit from other Qt classes like [QValidator](https://doc.qt.io/qt-5/qvalidator.html).
+- ClientModel is used by the main application `bitcoingui` and several models like `peertablemodel`.
-### \*page.(h/cpp)
+#### \*page.(h/cpp)
-A controller. `:NAMEpage.cpp` generally includes `:NAMEmodel.h` and `forms/:NAME.page.ui` with a similar `:NAME`.
+- A controller. `:NAMEpage.cpp` generally includes `:NAMEmodel.h` and `forms/:NAME.page.ui` with a similar `:NAME`.
-### \*dialog.(h/cpp)
+#### \*dialog.(h/cpp)
-Various dialogs, e.g. to open a URL. Inherit from [QDialog](https://doc.qt.io/qt-5/qdialog.html).
+- Various dialogs, e.g. to open a URL. Inherit from [QDialog](https://doc.qt.io/qt-5/qdialog.html).
-### paymentserver.(h/cpp)
+#### paymentserver.(h/cpp)
-Used to process BIP21 payment URI requests. Also handles URI based application switching (e.g. when following a bitcoin:... link from a browser).
+- (Deprecated) Used to process BIP21 payment URI requests. Also handles URI-based application switching (e.g. when following a bitcoin:... link from a browser).
-### walletview.(h/cpp)
+#### walletview.(h/cpp)
-Represents the view to a single wallet.
+- Represents the view to a single wallet.
-### Other .h/cpp files
+#### Other .h/cpp files
* UI elements like BitcoinAmountField, which inherit from QWidget.
* `bitcoinstrings.cpp`: automatically generated
-* `bitcoinunits.(h/cpp)`: BTC / mBTC / etc handling
+* `bitcoinunits.(h/cpp)`: BTC / mBTC / etc. handling
* `callback.h`
-* `guiconstants.h`: UI colors, app name, etc
+* `guiconstants.h`: UI colors, app name, etc.
* `guiutil.h`: several helper functions
* `macdockiconhandler.(h/mm)`: macOS dock icon handler
* `macnotificationhandler.(h/mm)`: display notifications in macOS
## Contribute
-See [CONTRIBUTING.md](/CONTRIBUTING.md) for general guidelines. Specifically for Qt:
+See [CONTRIBUTING.md](/CONTRIBUTING.md) for general guidelines.
+
+**Note:** Do not change `local/bitcoin_en.ts`. It is updated [automatically](/doc/translation_process.md#writing-code-with-translations).
+
+## Using Qt Creator as an IDE
+
+[Qt Creator](https://www.qt.io/product/development-tools) is a powerful tool which packages a UI designer tool (Qt Designer) and a C++ IDE into one application. This is especially useful if you want to change the UI layout.
+
+#### Download Qt Creator
+
+On Unix and macOS, Qt Creator can be installed through your package manager. Alternatively, you can download a binary from the [Qt Website](https://www.qt.io/download/).
+
+**Note:** If installing from a binary grabbed from the Qt Website: During the installation process, uncheck everything except for `Qt Creator`.
+
+##### macOS
+
+```sh
+brew install qt-creator
+```
+
+##### Ubuntu & Debian
+
+```sh
+sudo apt-get install qtcreator
+```
+
+#### Setup Qt Creator
-* don't change `local/bitcoin_en.ts`; this happens [automatically](/doc/translation_process.md#writing-code-with-translations)
+1. Make sure you've installed all dependencies specified in your systems build instructions
+2. Follow the compile instructions for your system, run `./configure` with the `--enable-debug` flag
+3. Start Qt Creator. At the start page, do: `New` -> `Import Project` -> `Import Existing Project`
+4. Enter `bitcoin-qt` as the Project Name and enter the absolute path to `src/qt` as Location
+5. Check over the file selection, you may need to select the `forms` directory (necessary if you intend to edit *.ui files)
+6. Confirm the `Summary` page
+7. In the `Projects` tab, select `Manage Kits...`
-## Using Qt Creator as IDE
+ **macOS**
+ - Under `Kits`: select the default "Desktop" kit
+ - Under `Compilers`: select `"Clang (x86 64bit in /usr/bin)"`
+ - Under `Debuggers`: select `"LLDB"` as debugger (you might need to set the path to your LLDB installation)
-You can use Qt Creator as an IDE. This is especially useful if you want to change
-the UI layout.
+ **Ubuntu & Debian**
-Download and install the community edition of [Qt Creator](https://www.qt.io/download/).
-Uncheck everything except Qt Creator during the installation process.
+ Note: Some of these options may already be set
-Instructions for macOS:
+ - Under `Kits`: select the default "Desktop" kit
+ - Under `Compilers`: select `"GCC (x86 64bit in /usr/bin)"`
+ - Under `Debuggers`: select `"GDB"` as debugger
-1. Make sure you installed everything through Homebrew mentioned in the [macOS build instructions](/doc/build-osx.md)
-2. Use `./configure` with the `--enable-debug` flag
-3. In Qt Creator do "New Project" -> Import Project -> Import Existing Project
-4. Enter "bitcoin-qt" as project name, enter src/qt as location
-5. Leave the file selection as it is
-6. Confirm the "summary page"
-7. In the "Projects" tab select "Manage Kits..."
-8. Select the default "Desktop" kit and select "Clang (x86 64bit in /usr/bin)" as compiler
-9. Select LLDB as debugger (you might need to set the path to your installation)
-10. Start debugging with Qt Creator (you might need to the executable to "bitcoin-qt" under "Run", which is where you can also add command line arguments)
+8. While in the `Projects` tab, ensure that you have the `bitcoin-qt` executable specified under `Run`
+ - If the executable is not specified: click `"Choose..."`, navigate to `src/qt`, and select `bitcoin-qt`
+9. You're all set! Start developing, building, and debugging the Bitcoin Core GUI
diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp
index 0fa52359a8..ab6168a541 100644
--- a/src/qt/addressbookpage.cpp
+++ b/src/qt/addressbookpage.cpp
@@ -56,7 +56,7 @@ protected:
};
AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::AddressBookPage),
model(nullptr),
mode(_mode),
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp
index c2349b78f9..5b1330b81b 100644
--- a/src/qt/askpassphrasedialog.cpp
+++ b/src/qt/askpassphrasedialog.cpp
@@ -20,7 +20,7 @@
#include <QPushButton>
AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::AskPassphraseDialog),
mode(_mode),
model(nullptr),
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 8efb0e35d0..d6d5ba6968 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -51,6 +51,7 @@
#include <QThread>
#include <QTimer>
#include <QTranslator>
+#include <QtGlobal>
#if defined(QT_STATICPLUGIN)
#include <QtPlugin>
@@ -466,6 +467,13 @@ int GuiMain(int argc, char* argv[])
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
+#if (QT_VERSION <= QT_VERSION_CHECK(5, 9, 8)) && defined(Q_OS_MACOS)
+ const auto os_name = QSysInfo::prettyProductName();
+ if (os_name.startsWith("macOS 11") || os_name.startsWith("macOS 10.16")) {
+ QApplication::setStyle("fusion");
+ }
+#endif
+
BitcoinApplication app;
/// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these
diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp
index 08abef7866..ca78c96d70 100644
--- a/src/qt/coincontroldialog.cpp
+++ b/src/qt/coincontroldialog.cpp
@@ -42,7 +42,7 @@ bool CCoinControlWidgetItem::operator<(const QTreeWidgetItem &other) const {
}
CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _model, const PlatformStyle *_platformStyle, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::CoinControlDialog),
m_coin_control(coin_control),
model(_model),
diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp
index 70f080f4de..1467801522 100644
--- a/src/qt/createwalletdialog.cpp
+++ b/src/qt/createwalletdialog.cpp
@@ -9,10 +9,12 @@
#include <qt/createwalletdialog.h>
#include <qt/forms/ui_createwalletdialog.h>
+#include <qt/guiutil.h>
+
#include <QPushButton>
CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::CreateWalletDialog)
{
ui->setupUi(this);
diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp
index cd25f856a2..e51ed9e656 100644
--- a/src/qt/editaddressdialog.cpp
+++ b/src/qt/editaddressdialog.cpp
@@ -13,7 +13,7 @@
EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::EditAddressDialog),
mapper(nullptr),
mode(_mode),
diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui
index bce578a158..3831852185 100644
--- a/src/qt/forms/debugwindow.ui
+++ b/src/qt/forms/debugwindow.ui
@@ -1079,7 +1079,7 @@
<item row="1" column="0">
<widget class="QLabel" name="peerConnectionTypeLabel">
<property name="toolTip">
- <string>The type of peer connection:&lt;ul&gt;&lt;li&gt;Inbound: initiated by peer&lt;/li&gt;&lt;li&gt;Outbound Full Relay: default&lt;/li&gt;&lt;li&gt;Outbound Block Relay: does not relay transactions or addresses&lt;/li&gt;&lt;li&gt;Outbound Manual: added using RPC %1 or %2/%3 configuration options&lt;/li&gt;&lt;li&gt;Outbound Feeler: short-lived, for testing addresses&lt;/li&gt;&lt;li&gt;Outbound Address Fetch: short-lived, for soliciting addresses&lt;/li&gt;&lt;/ul&gt;</string>
+ <string>The type of peer connection: %1</string>
</property>
<property name="text">
<string>Connection Type</string>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 430ecd322f..376acf0963 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -40,12 +40,14 @@
#include <QFontDatabase>
#include <QFontMetrics>
#include <QGuiApplication>
+#include <QJsonObject>
#include <QKeyEvent>
#include <QLineEdit>
#include <QList>
#include <QLocale>
#include <QMenu>
#include <QMouseEvent>
+#include <QPluginLoader>
#include <QProgressDialog>
#include <QScreen>
#include <QSettings>
@@ -764,10 +766,10 @@ QString NetworkToQString(Network net)
assert(false);
}
-QString ConnectionTypeToQString(ConnectionType conn_type)
+QString ConnectionTypeToQString(ConnectionType conn_type, bool relay_txes)
{
switch (conn_type) {
- case ConnectionType::INBOUND: return QObject::tr("Inbound");
+ case ConnectionType::INBOUND: return relay_txes ? QObject::tr("Inbound Full Relay") : QObject::tr("Inbound Block Relay");
case ConnectionType::OUTBOUND_FULL_RELAY: return QObject::tr("Outbound Full Relay");
case ConnectionType::BLOCK_RELAY: return QObject::tr("Outbound Block Relay");
case ConnectionType::MANUAL: return QObject::tr("Outbound Manual");
@@ -936,6 +938,20 @@ void LogQtInfo()
const std::string plugin_link{"dynamic"};
#endif
LogPrintf("Qt %s (%s), plugin=%s (%s)\n", qVersion(), qt_link, QGuiApplication::platformName().toStdString(), plugin_link);
+ const auto static_plugins = QPluginLoader::staticPlugins();
+ if (static_plugins.empty()) {
+ LogPrintf("No static plugins.\n");
+ } else {
+ LogPrintf("Static plugins:\n");
+ for (const QStaticPlugin& p : static_plugins) {
+ QJsonObject meta_data = p.metaData();
+ const std::string plugin_class = meta_data.take(QString("className")).toString().toStdString();
+ const int plugin_version = meta_data.take(QString("version")).toInt();
+ LogPrintf(" %s, version %d\n", plugin_class, plugin_version);
+ }
+ }
+
+ LogPrintf("Style: %s / %s\n", QApplication::style()->objectName().toStdString(), QApplication::style()->metaObject()->className());
LogPrintf("System: %s, %s\n", QSysInfo::prettyProductName().toStdString(), QSysInfo::buildAbi().toStdString());
for (const QScreen* s : QGuiApplication::screens()) {
LogPrintf("Screen: %s %dx%d, pixel ratio=%.1f\n", s->name().toStdString(), s->size().width(), s->size().height(), s->devicePixelRatio());
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index c471b888f7..edfb5b13a2 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -45,6 +45,9 @@ QT_END_NAMESPACE
*/
namespace GUIUtil
{
+ // Use this flags to prevent a "What's This" button in the title bar of the dialog on Windows.
+ constexpr auto dialog_flags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
+
// Create human-readable string from date
QString dateTimeStr(const QDateTime &datetime);
QString dateTimeStr(qint64 nTime);
@@ -230,7 +233,7 @@ namespace GUIUtil
QString NetworkToQString(Network net);
/** Convert enum ConnectionType to QString */
- QString ConnectionTypeToQString(ConnectionType conn_type);
+ QString ConnectionTypeToQString(ConnectionType conn_type, bool relay_txes);
/** Convert seconds into a QString with days, hours, mins, secs */
QString formatDurationStr(int secs);
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
index 235722d091..aa6b2665fa 100644
--- a/src/qt/intro.cpp
+++ b/src/qt/intro.cpp
@@ -119,7 +119,7 @@ int GetPruneTargetGB()
} // namespace
Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_size_gb) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::Intro),
thread(nullptr),
signalled(false),
diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp
index 9a3d43c2a6..10bf82d532 100644
--- a/src/qt/openuridialog.cpp
+++ b/src/qt/openuridialog.cpp
@@ -11,7 +11,7 @@
#include <QUrl>
OpenURIDialog::OpenURIDialog(QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::OpenURIDialog)
{
ui->setupUi(this);
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 416b9c83c9..7b8d7871ec 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -29,7 +29,7 @@
#include <QTimer>
OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::OptionsDialog),
model(nullptr),
mapper(nullptr)
diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp
index 58167d4bb4..55ab6046cf 100644
--- a/src/qt/psbtoperationsdialog.cpp
+++ b/src/qt/psbtoperationsdialog.cpp
@@ -19,7 +19,7 @@
PSBTOperationsDialog::PSBTOperationsDialog(
- QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent),
+ QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent, GUIUtil::dialog_flags),
m_ui(new Ui::PSBTOperationsDialog),
m_wallet_model(wallet_model),
m_client_model(client_model)
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index 1a9cd78d1e..0aea920c86 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -8,6 +8,7 @@
#include <qt/forms/ui_receivecoinsdialog.h>
#include <qt/addresstablemodel.h>
+#include <qt/guiutil.h>
#include <qt/optionsmodel.h>
#include <qt/platformstyle.h>
#include <qt/receiverequestdialog.h>
@@ -21,7 +22,7 @@
#include <QTextDocument>
ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::ReceiveCoinsDialog),
columnResizingFixer(nullptr),
model(nullptr),
diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp
index bef3edc5fe..78ae5c07da 100644
--- a/src/qt/receiverequestdialog.cpp
+++ b/src/qt/receiverequestdialog.cpp
@@ -19,7 +19,7 @@
#endif
ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::ReceiveRequestDialog),
model(nullptr)
{
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index df98dbbc99..a252685d2f 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -16,9 +16,10 @@
#include <chainparams.h>
#include <interfaces/node.h>
#include <netbase.h>
-#include <rpc/server.h>
#include <rpc/client.h>
+#include <rpc/server.h>
#include <util/strencodings.h>
+#include <util/string.h>
#include <util/system.h>
#include <util/threadnames.h>
@@ -459,11 +460,22 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty
ui->splitter->restoreState(settings.value("PeersTabSplitterSizes").toByteArray());
- QChar nonbreaking_hyphen(8209);
+ constexpr QChar nonbreaking_hyphen(8209);
+ const std::vector<QString> CONNECTION_TYPE_DOC{
+ tr("Inbound Full/Block Relay: initiated by peer"),
+ tr("Outbound Full Relay: default"),
+ tr("Outbound Block Relay: does not relay transactions or addresses"),
+ tr("Outbound Manual: added using RPC %1 or %2/%3 configuration options")
+ .arg("addnode")
+ .arg(QString(nonbreaking_hyphen) + "addnode")
+ .arg(QString(nonbreaking_hyphen) + "connect"),
+ tr("Outbound Feeler: short-lived, for testing addresses"),
+ tr("Outbound Address Fetch: short-lived, for soliciting addresses")};
+ const QString list{"<ul><li>" + Join(CONNECTION_TYPE_DOC, QString("</li><li>")) + "</li></ul>"};
+ ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg(list));
ui->dataDir->setToolTip(ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir"));
ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(QString(nonbreaking_hyphen) + "blocksdir"));
ui->openDebugLogfileButton->setToolTip(ui->openDebugLogfileButton->toolTip().arg(PACKAGE_NAME));
- ui->peerConnectionTypeLabel->setToolTip(ui->peerConnectionTypeLabel->toolTip().arg("addnode").arg(QString(nonbreaking_hyphen) + "addnode").arg(QString(nonbreaking_hyphen) + "connect"));
if (platformStyle->getImagesOnButtons()) {
ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export"));
@@ -1108,7 +1120,7 @@ void RPCConsole::updateDetailWidget()
ui->timeoffset->setText(GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
ui->peerVersion->setText(QString::number(stats->nodeStats.nVersion));
ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer));
- ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type));
+ ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, stats->nodeStats.fRelayTxes));
ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network));
if (stats->nodeStats.m_permissionFlags == PF_NONE) {
ui->peerPermissions->setText(tr("N/A"));
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index 9eedd8cf75..611f3584c3 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -55,7 +55,7 @@ int getIndexForConfTarget(int target) {
}
SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::SendCoinsDialog),
clientModel(nullptr),
model(nullptr),
diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp
index 6c110f0688..2e7df60574 100644
--- a/src/qt/signverifymessagedialog.cpp
+++ b/src/qt/signverifymessagedialog.cpp
@@ -19,7 +19,7 @@
#include <QClipboard>
SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::SignVerifyMessageDialog),
model(nullptr),
platformStyle(_platformStyle)
diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp
index 24d7735447..0d9928d363 100644
--- a/src/qt/test/rpcnestedtests.cpp
+++ b/src/qt/test/rpcnestedtests.cpp
@@ -33,7 +33,7 @@ static RPCHelpMan rpcNestedTest_rpc()
}
static const CRPCCommand vRPCCommands[] = {
- {"test", "rpcNestedTest", &rpcNestedTest_rpc, {"arg1", "arg2", "arg3"}},
+ {"test", &rpcNestedTest_rpc},
};
void RPCNestedTests::rpcNestedTests()
diff --git a/src/qt/transactiondescdialog.cpp b/src/qt/transactiondescdialog.cpp
index b547bf7645..6f31d8463f 100644
--- a/src/qt/transactiondescdialog.cpp
+++ b/src/qt/transactiondescdialog.cpp
@@ -11,7 +11,7 @@
#include <QModelIndex>
TransactionDescDialog::TransactionDescDialog(const QModelIndex &idx, QWidget *parent) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::TransactionDescDialog)
{
ui->setupUi(this);
diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp
index b7f85446f4..05499b2a41 100644
--- a/src/qt/utilitydialog.cpp
+++ b/src/qt/utilitydialog.cpp
@@ -29,7 +29,7 @@
/** "Help message" or "About" dialog box */
HelpMessageDialog::HelpMessageDialog(QWidget *parent, bool about) :
- QDialog(parent),
+ QDialog(parent, GUIUtil::dialog_flags),
ui(new Ui::HelpMessageDialog)
{
ui->setupUi(this);
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 16a8f40889..a83b45ae1d 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2480,41 +2480,41 @@ void RegisterBlockchainRPCCommands(CRPCTable &t)
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "blockchain", "getblockchaininfo", &getblockchaininfo, {} },
- { "blockchain", "getchaintxstats", &getchaintxstats, {"nblocks", "blockhash"} },
- { "blockchain", "getblockstats", &getblockstats, {"hash_or_height", "stats"} },
- { "blockchain", "getbestblockhash", &getbestblockhash, {} },
- { "blockchain", "getblockcount", &getblockcount, {} },
- { "blockchain", "getblock", &getblock, {"blockhash","verbosity|verbose"} },
- { "blockchain", "getblockhash", &getblockhash, {"height"} },
- { "blockchain", "getblockheader", &getblockheader, {"blockhash","verbose"} },
- { "blockchain", "getchaintips", &getchaintips, {} },
- { "blockchain", "getdifficulty", &getdifficulty, {} },
- { "blockchain", "getmempoolancestors", &getmempoolancestors, {"txid","verbose"} },
- { "blockchain", "getmempooldescendants", &getmempooldescendants, {"txid","verbose"} },
- { "blockchain", "getmempoolentry", &getmempoolentry, {"txid"} },
- { "blockchain", "getmempoolinfo", &getmempoolinfo, {} },
- { "blockchain", "getrawmempool", &getrawmempool, {"verbose", "mempool_sequence"} },
- { "blockchain", "gettxout", &gettxout, {"txid","n","include_mempool"} },
- { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type"} },
- { "blockchain", "pruneblockchain", &pruneblockchain, {"height"} },
- { "blockchain", "savemempool", &savemempool, {} },
- { "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
-
- { "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
- { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
- { "blockchain", "getblockfilter", &getblockfilter, {"blockhash", "filtertype"} },
+{ // category actor (function)
+ // --------------------- ------------------------
+ { "blockchain", &getblockchaininfo, },
+ { "blockchain", &getchaintxstats, },
+ { "blockchain", &getblockstats, },
+ { "blockchain", &getbestblockhash, },
+ { "blockchain", &getblockcount, },
+ { "blockchain", &getblock, },
+ { "blockchain", &getblockhash, },
+ { "blockchain", &getblockheader, },
+ { "blockchain", &getchaintips, },
+ { "blockchain", &getdifficulty, },
+ { "blockchain", &getmempoolancestors, },
+ { "blockchain", &getmempooldescendants, },
+ { "blockchain", &getmempoolentry, },
+ { "blockchain", &getmempoolinfo, },
+ { "blockchain", &getrawmempool, },
+ { "blockchain", &gettxout, },
+ { "blockchain", &gettxoutsetinfo, },
+ { "blockchain", &pruneblockchain, },
+ { "blockchain", &savemempool, },
+ { "blockchain", &verifychain, },
+
+ { "blockchain", &preciousblock, },
+ { "blockchain", &scantxoutset, },
+ { "blockchain", &getblockfilter, },
/* Not shown in help */
- { "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },
- { "hidden", "reconsiderblock", &reconsiderblock, {"blockhash"} },
- { "hidden", "waitfornewblock", &waitfornewblock, {"timeout"} },
- { "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} },
- { "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} },
- { "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} },
- { "hidden", "dumptxoutset", &dumptxoutset, {"path"} },
+ { "hidden", &invalidateblock, },
+ { "hidden", &reconsiderblock, },
+ { "hidden", &waitfornewblock, },
+ { "hidden", &waitforblock, },
+ { "hidden", &waitforblockheight, },
+ { "hidden", &syncwithvalidationinterfacequeue, },
+ { "hidden", &dumptxoutset, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 042005b9a6..d1eb849b7e 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -210,14 +210,9 @@ public:
CRPCConvertTable::CRPCConvertTable()
{
- const unsigned int n_elem =
- (sizeof(vRPCConvertParams) / sizeof(vRPCConvertParams[0]));
-
- for (unsigned int i = 0; i < n_elem; i++) {
- members.insert(std::make_pair(vRPCConvertParams[i].methodName,
- vRPCConvertParams[i].paramIdx));
- membersByName.insert(std::make_pair(vRPCConvertParams[i].methodName,
- vRPCConvertParams[i].paramName));
+ for (const auto& cp : vRPCConvertParams) {
+ members.emplace(cp.methodName, cp.paramIdx);
+ membersByName.emplace(cp.methodName, cp.paramName);
}
}
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index da73d38af2..50987a735b 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -1232,24 +1232,24 @@ void RegisterMiningRPCCommands(CRPCTable &t)
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "mining", "getnetworkhashps", &getnetworkhashps, {"nblocks","height"} },
- { "mining", "getmininginfo", &getmininginfo, {} },
- { "mining", "prioritisetransaction", &prioritisetransaction, {"txid","dummy","fee_delta"} },
- { "mining", "getblocktemplate", &getblocktemplate, {"template_request"} },
- { "mining", "submitblock", &submitblock, {"hexdata","dummy"} },
- { "mining", "submitheader", &submitheader, {"hexdata"} },
+{ // category actor (function)
+ // --------------------- -----------------------
+ { "mining", &getnetworkhashps, },
+ { "mining", &getmininginfo, },
+ { "mining", &prioritisetransaction, },
+ { "mining", &getblocktemplate, },
+ { "mining", &submitblock, },
+ { "mining", &submitheader, },
- { "generating", "generatetoaddress", &generatetoaddress, {"nblocks","address","maxtries"} },
- { "generating", "generatetodescriptor", &generatetodescriptor, {"num_blocks","descriptor","maxtries"} },
- { "generating", "generateblock", &generateblock, {"output","transactions"} },
+ { "generating", &generatetoaddress, },
+ { "generating", &generatetodescriptor, },
+ { "generating", &generateblock, },
- { "util", "estimatesmartfee", &estimatesmartfee, {"conf_target", "estimate_mode"} },
+ { "util", &estimatesmartfee, },
- { "hidden", "estimaterawfee", &estimaterawfee, {"conf_target", "threshold"} },
- { "hidden", "generate", &generate, {} },
+ { "hidden", &estimaterawfee, },
+ { "hidden", &generate, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index 215e48ca26..b75a7b8d26 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -699,23 +699,23 @@ void RegisterMiscRPCCommands(CRPCTable &t)
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "control", "getmemoryinfo", &getmemoryinfo, {"mode"} },
- { "control", "logging", &logging, {"include", "exclude"}},
- { "util", "validateaddress", &validateaddress, {"address"} },
- { "util", "createmultisig", &createmultisig, {"nrequired","keys","address_type"} },
- { "util", "deriveaddresses", &deriveaddresses, {"descriptor", "range"} },
- { "util", "getdescriptorinfo", &getdescriptorinfo, {"descriptor"} },
- { "util", "verifymessage", &verifymessage, {"address","signature","message"} },
- { "util", "signmessagewithprivkey", &signmessagewithprivkey, {"privkey","message"} },
- { "util", "getindexinfo", &getindexinfo, {"index_name"} },
+{ // category actor (function)
+ // --------------------- ------------------------
+ { "control", &getmemoryinfo, },
+ { "control", &logging, },
+ { "util", &validateaddress, },
+ { "util", &createmultisig, },
+ { "util", &deriveaddresses, },
+ { "util", &getdescriptorinfo, },
+ { "util", &verifymessage, },
+ { "util", &signmessagewithprivkey, },
+ { "util", &getindexinfo, },
/* Not shown in help */
- { "hidden", "setmocktime", &setmocktime, {"timestamp"}},
- { "hidden", "mockscheduler", &mockscheduler, {"delta_time"}},
- { "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
- { "hidden", "echojson", &echojson, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
+ { "hidden", &setmocktime, },
+ { "hidden", &mockscheduler, },
+ { "hidden", &echo, },
+ { "hidden", &echojson, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp
index cfca8b4ad4..47d77b341a 100644
--- a/src/rpc/net.cpp
+++ b/src/rpc/net.cpp
@@ -941,24 +941,24 @@ void RegisterNetRPCCommands(CRPCTable &t)
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "network", "getconnectioncount", &getconnectioncount, {} },
- { "network", "ping", &ping, {} },
- { "network", "getpeerinfo", &getpeerinfo, {} },
- { "network", "addnode", &addnode, {"node","command"} },
- { "network", "disconnectnode", &disconnectnode, {"address", "nodeid"} },
- { "network", "getaddednodeinfo", &getaddednodeinfo, {"node"} },
- { "network", "getnettotals", &getnettotals, {} },
- { "network", "getnetworkinfo", &getnetworkinfo, {} },
- { "network", "setban", &setban, {"subnet", "command", "bantime", "absolute"} },
- { "network", "listbanned", &listbanned, {} },
- { "network", "clearbanned", &clearbanned, {} },
- { "network", "setnetworkactive", &setnetworkactive, {"state"} },
- { "network", "getnodeaddresses", &getnodeaddresses, {"count"} },
-
- { "hidden", "addconnection", &addconnection, {"address", "connection_type"} },
- { "hidden", "addpeeraddress", &addpeeraddress, {"address", "port"} },
+{ // category actor
+ // --------------------- -----------------------
+ { "network", &getconnectioncount, },
+ { "network", &ping, },
+ { "network", &getpeerinfo, },
+ { "network", &addnode, },
+ { "network", &disconnectnode, },
+ { "network", &getaddednodeinfo, },
+ { "network", &getnettotals, },
+ { "network", &getnetworkinfo, },
+ { "network", &setban, },
+ { "network", &listbanned, },
+ { "network", &clearbanned, },
+ { "network", &setnetworkactive, },
+ { "network", &getnodeaddresses, },
+
+ { "hidden", &addconnection, },
+ { "hidden", &addpeeraddress, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/protocol.h b/src/rpc/protocol.h
index c8ceb2c186..fc00a1efad 100644
--- a/src/rpc/protocol.h
+++ b/src/rpc/protocol.h
@@ -79,6 +79,7 @@ enum RPCErrorCode
RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked
RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified
RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded)
+ RPC_WALLET_ALREADY_LOADED = -35, //!< This same wallet is already loaded
//! Backwards compatible aliases
RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME,
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 7b9cab75d6..e478f12ab5 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -816,10 +816,11 @@ static RPCHelpMan sendrawtransaction()
{
return RPCHelpMan{"sendrawtransaction",
"\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n"
- "\nNote that the transaction will be sent unconditionally to all peers, so using this\n"
+ "\nThe transaction will be sent unconditionally to all peers, so using sendrawtransaction\n"
"for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n"
"nodes will normally not rebroadcast non-wallet transactions already in their mempool.\n"
- "\nAlso see createrawtransaction and signrawtransactionwithkey calls.\n",
+ "\nA specific exception, RPC_TRANSACTION_ALREADY_IN_CHAIN, may throw if the transaction cannot be added to the mempool.\n"
+ "\nRelated RPCs: createrawtransaction, signrawtransactionwithkey\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
{"maxfeerate", RPCArg::Type::AMOUNT, /* default */ FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK()),
@@ -1859,27 +1860,27 @@ void RegisterRawTransactionRPCCommands(CRPCTable &t)
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "rawtransactions", "getrawtransaction", &getrawtransaction, {"txid","verbose","blockhash"} },
- { "rawtransactions", "createrawtransaction", &createrawtransaction, {"inputs","outputs","locktime","replaceable"} },
- { "rawtransactions", "decoderawtransaction", &decoderawtransaction, {"hexstring","iswitness"} },
- { "rawtransactions", "decodescript", &decodescript, {"hexstring"} },
- { "rawtransactions", "sendrawtransaction", &sendrawtransaction, {"hexstring","maxfeerate"} },
- { "rawtransactions", "combinerawtransaction", &combinerawtransaction, {"txs"} },
- { "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} },
- { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","maxfeerate"} },
- { "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} },
- { "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} },
- { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} },
- { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} },
- { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} },
- { "rawtransactions", "utxoupdatepsbt", &utxoupdatepsbt, {"psbt", "descriptors"} },
- { "rawtransactions", "joinpsbts", &joinpsbts, {"txs"} },
- { "rawtransactions", "analyzepsbt", &analyzepsbt, {"psbt"} },
-
- { "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} },
- { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} },
+{ // category actor (function)
+ // --------------------- -----------------------
+ { "rawtransactions", &getrawtransaction, },
+ { "rawtransactions", &createrawtransaction, },
+ { "rawtransactions", &decoderawtransaction, },
+ { "rawtransactions", &decodescript, },
+ { "rawtransactions", &sendrawtransaction, },
+ { "rawtransactions", &combinerawtransaction, },
+ { "rawtransactions", &signrawtransactionwithkey, },
+ { "rawtransactions", &testmempoolaccept, },
+ { "rawtransactions", &decodepsbt, },
+ { "rawtransactions", &combinepsbt, },
+ { "rawtransactions", &finalizepsbt, },
+ { "rawtransactions", &createpsbt, },
+ { "rawtransactions", &converttopsbt, },
+ { "rawtransactions", &utxoupdatepsbt, },
+ { "rawtransactions", &joinpsbts, },
+ { "rawtransactions", &analyzepsbt, },
+
+ { "blockchain", &gettxoutproof, },
+ { "blockchain", &verifytxoutproof, },
};
// clang-format on
for (const auto& c : commands) {
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index f32d9abac6..9a9b3713f3 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -144,8 +144,13 @@ static RPCHelpMan help()
[&](const RPCHelpMan& self, const JSONRPCRequest& jsonRequest) -> UniValue
{
std::string strCommand;
- if (jsonRequest.params.size() > 0)
+ if (jsonRequest.params.size() > 0) {
strCommand = jsonRequest.params[0].get_str();
+ }
+ if (strCommand == "dump_all_command_conversions") {
+ // Used for testing only, undocumented
+ return tableRPC.dumpArgMap();
+ }
return tableRPC.help(strCommand, jsonRequest);
},
@@ -244,13 +249,13 @@ static RPCHelpMan getrpcinfo()
// clang-format off
static const CRPCCommand vRPCCommands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
+{ // category actor (function)
+ // --------------------- -----------------------
/* Overall control/query calls */
- { "control", "getrpcinfo", &getrpcinfo, {} },
- { "control", "help", &help, {"command"} },
- { "control", "stop", &stop, {"wait"} },
- { "control", "uptime", &uptime, {} },
+ { "control", &getrpcinfo, },
+ { "control", &help, },
+ { "control", &stop, },
+ { "control", &uptime, },
};
// clang-format on
@@ -479,6 +484,18 @@ std::vector<std::string> CRPCTable::listCommands() const
return commandList;
}
+UniValue CRPCTable::dumpArgMap() const
+{
+ UniValue ret{UniValue::VARR};
+ for (const auto& cmd : mapCommands) {
+ for (const auto& c : cmd.second) {
+ const auto help = RpcMethodFnType(c->unique_id)();
+ help.AppendArgMap(ret);
+ }
+ }
+ return ret;
+}
+
void RPCSetTimerInterfaceIfUnset(RPCTimerInterface *iface)
{
if (!timerInterface)
diff --git a/src/rpc/server.h b/src/rpc/server.h
index 040192b455..fe5a791e1e 100644
--- a/src/rpc/server.h
+++ b/src/rpc/server.h
@@ -103,7 +103,7 @@ public:
}
//! Simplified constructor taking plain RpcMethodFnType function pointer.
- CRPCCommand(std::string category, std::string name_in, RpcMethodFnType fn, std::vector<std::string> args_in)
+ CRPCCommand(std::string category, RpcMethodFnType fn)
: CRPCCommand(
category,
fn().m_name,
@@ -111,8 +111,6 @@ public:
fn().GetArgNames(),
intptr_t(fn))
{
- CHECK_NONFATAL(fn().m_name == name_in);
- CHECK_NONFATAL(fn().GetArgNames() == args_in);
}
std::string category;
@@ -147,6 +145,10 @@ public:
*/
std::vector<std::string> listCommands() const;
+ /**
+ * Return all named arguments that need to be converted by the client from string to another JSON type
+ */
+ UniValue dumpArgMap() const;
/**
* Appends a CRPCCommand to the dispatch table.
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 31072114da..bfdba5253c 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -549,6 +549,24 @@ std::string RPCHelpMan::ToString() const
return ret;
}
+void RPCHelpMan::AppendArgMap(UniValue& arr) const
+{
+ for (int i{0}; i < int(m_args.size()); ++i) {
+ const auto& arg = m_args.at(i);
+ std::vector<std::string> arg_names;
+ boost::split(arg_names, arg.m_names, boost::is_any_of("|"));
+ for (const auto& arg_name : arg_names) {
+ UniValue map{UniValue::VARR};
+ map.push_back(m_name);
+ map.push_back(i);
+ map.push_back(arg_name);
+ map.push_back(arg.m_type == RPCArg::Type::STR ||
+ arg.m_type == RPCArg::Type::STR_HEX);
+ arr.push_back(map);
+ }
+ }
+}
+
std::string RPCArg::GetFirstName() const
{
return m_names.substr(0, m_names.find("|"));
diff --git a/src/rpc/util.h b/src/rpc/util.h
index 942c243718..444a013ca1 100644
--- a/src/rpc/util.h
+++ b/src/rpc/util.h
@@ -336,6 +336,8 @@ public:
RPCHelpMan(std::string name, std::string description, std::vector<RPCArg> args, RPCResults results, RPCExamples examples, RPCMethodImpl fun);
std::string ToString() const;
+ /** Append the named args that need to be converted from string to another JSON type */
+ void AppendArgMap(UniValue& arr) const;
UniValue HandleRequest(const JSONRPCRequest& request)
{
Check(request);
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index 5d6a33d7c2..e7cc0f5297 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -60,10 +60,12 @@ void fuzz_target(const std::vector<uint8_t>& buffer, const std::string& LIMIT_TO
return;
}
CNode& p2p_node = *ConsumeNodeAsUniquePtr(fuzzed_data_provider).release();
- FillNode(fuzzed_data_provider, p2p_node);
- p2p_node.fSuccessfullyConnected = true;
+
+ const bool successfully_connected{true};
+ p2p_node.fSuccessfullyConnected = successfully_connected;
connman.AddTestNode(p2p_node);
g_setup->m_node.peerman->InitializeNode(&p2p_node);
+ FillNode(fuzzed_data_provider, p2p_node, /* init_version */ successfully_connected);
const auto mock_time = ConsumeTime(fuzzed_data_provider);
SetMockTime(mock_time);
diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp
index d0d0e19694..810f0aac92 100644
--- a/src/test/fuzz/process_messages.cpp
+++ b/src/test/fuzz/process_messages.cpp
@@ -45,11 +45,12 @@ FUZZ_TARGET_INIT(process_messages, initialize_process_messages)
for (int i = 0; i < num_peers_to_add; ++i) {
peers.push_back(ConsumeNodeAsUniquePtr(fuzzed_data_provider, i).release());
CNode& p2p_node = *peers.back();
- FillNode(fuzzed_data_provider, p2p_node);
- p2p_node.fSuccessfullyConnected = true;
+ const bool successfully_connected{true};
+ p2p_node.fSuccessfullyConnected = successfully_connected;
p2p_node.fPauseSend = false;
g_setup->m_node.peerman->InitializeNode(&p2p_node);
+ FillNode(fuzzed_data_provider, p2p_node, /* init_version */ successfully_connected);
connman.AddTestNode(p2p_node);
}
diff --git a/src/test/fuzz/util.cpp b/src/test/fuzz/util.cpp
new file mode 100644
index 0000000000..0a541e4186
--- /dev/null
+++ b/src/test/fuzz/util.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) 2021 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 <test/fuzz/util.h>
+#include <version.h>
+
+void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept
+{
+ const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
+ const NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
+ const int32_t version = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(MIN_PEER_PROTO_VERSION, std::numeric_limits<int32_t>::max());
+ const bool filter_txs = fuzzed_data_provider.ConsumeBool();
+
+ node.nServices = remote_services;
+ node.m_permissionFlags = permission_flags;
+ if (init_version) {
+ node.nVersion = version;
+ node.SetCommonVersion(std::min(version, PROTOCOL_VERSION));
+ }
+ if (node.m_tx_relay != nullptr) {
+ LOCK(node.m_tx_relay->cs_filter);
+ node.m_tx_relay->fRelayTxes = filter_txs;
+ }
+}
diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h
index 5b8eacf051..e48771efa2 100644
--- a/src/test/fuzz/util.h
+++ b/src/test/fuzz/util.h
@@ -269,8 +269,8 @@ inline CNetAddr ConsumeNetAddr(FuzzedDataProvider& fuzzed_data_provider) noexcep
const Network network = fuzzed_data_provider.PickValueInArray({Network::NET_IPV4, Network::NET_IPV6, Network::NET_INTERNAL, Network::NET_ONION});
CNetAddr net_addr;
if (network == Network::NET_IPV4) {
- const in_addr v4_addr = {
- .s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
+ in_addr v4_addr = {};
+ v4_addr.s_addr = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
net_addr = CNetAddr{v4_addr};
} else if (network == Network::NET_IPV6) {
if (fuzzed_data_provider.remaining_bytes() >= 16) {
@@ -320,24 +320,9 @@ auto ConsumeNode(FuzzedDataProvider& fuzzed_data_provider, const std::optional<N
return CNode{node_id, local_services, socket, address, keyed_net_group, local_host_nonce, addr_bind, addr_name, conn_type, inbound_onion};
}
}
-inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = nullopt) { return ConsumeNode<true>(fdp, node_id_in); }
+inline std::unique_ptr<CNode> ConsumeNodeAsUniquePtr(FuzzedDataProvider& fdp, const std::optional<NodeId>& node_id_in = std::nullopt) { return ConsumeNode<true>(fdp, node_id_in); }
-inline void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, const std::optional<int32_t>& version_in = std::nullopt) noexcept
-{
- const ServiceFlags remote_services = ConsumeWeakEnum(fuzzed_data_provider, ALL_SERVICE_FLAGS);
- const NetPermissionFlags permission_flags = ConsumeWeakEnum(fuzzed_data_provider, ALL_NET_PERMISSION_FLAGS);
- const int32_t version = version_in.value_or(fuzzed_data_provider.ConsumeIntegral<int32_t>());
- const bool filter_txs = fuzzed_data_provider.ConsumeBool();
-
- node.nServices = remote_services;
- node.m_permissionFlags = permission_flags;
- node.nVersion = version;
- node.SetCommonVersion(version);
- if (node.m_tx_relay != nullptr) {
- LOCK(node.m_tx_relay->cs_filter);
- node.m_tx_relay->fRelayTxes = filter_txs;
- }
-}
+void FillNode(FuzzedDataProvider& fuzzed_data_provider, CNode& node, bool init_version) noexcept;
template <class T = const BasicTestingSetup>
std::unique_ptr<T> MakeFuzzingContext(const std::string& chain_name = CBaseChainParams::REGTEST, const std::vector<const char*>& extra_args = {})
diff --git a/src/validation.cpp b/src/validation.cpp
index cf6dd6d92d..38df71b994 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -3559,11 +3559,10 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS
// Check for duplicate
uint256 hash = block.GetHash();
BlockMap::iterator miSelf = m_block_index.find(hash);
- CBlockIndex *pindex = nullptr;
if (hash != chainparams.GetConsensus().hashGenesisBlock) {
if (miSelf != m_block_index.end()) {
// Block header is already known.
- pindex = miSelf->second;
+ CBlockIndex* pindex = miSelf->second;
if (ppindex)
*ppindex = pindex;
if (pindex->nStatus & BLOCK_FAILED_MASK) {
@@ -3632,8 +3631,7 @@ bool BlockManager::AcceptBlockHeader(const CBlockHeader& block, BlockValidationS
}
}
}
- if (pindex == nullptr)
- pindex = AddToBlockIndex(block);
+ CBlockIndex* pindex = AddToBlockIndex(block);
if (ppindex)
*ppindex = pindex;
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index a2dea84d17..10f89e3a6f 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -300,8 +300,26 @@ bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& group
******************************************************************************/
-void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
+void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only) {
+ // Compute the effective value first
+ const CAmount coin_fee = output.m_input_bytes < 0 ? 0 : m_effective_feerate.GetFee(output.m_input_bytes);
+ const CAmount ev = output.txout.nValue - coin_fee;
+
+ // Filter for positive only here before adding the coin
+ if (positive_only && ev <= 0) return;
+
m_outputs.push_back(output);
+ CInputCoin& coin = m_outputs.back();
+
+ coin.m_fee = coin_fee;
+ fee += coin.m_fee;
+
+ coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : m_long_term_feerate.GetFee(coin.m_input_bytes);
+ long_term_fee += coin.m_long_term_fee;
+
+ coin.effective_value = ev;
+ effective_value += coin.effective_value;
+
m_from_me &= from_me;
m_value += output.txout.nValue;
m_depth = std::min(m_depth, depth);
@@ -312,20 +330,6 @@ void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size
// descendants is the count as seen from the top ancestor, not the descendants as seen from the
// coin itself; thus, this value is counted as the max, not the sum
m_descendants = std::max(m_descendants, descendants);
- effective_value += output.effective_value;
- fee += output.m_fee;
- long_term_fee += output.m_long_term_fee;
-}
-
-std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
- auto it = m_outputs.begin();
- while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
- if (it == m_outputs.end()) return it;
- m_value -= output.txout.nValue;
- effective_value -= output.effective_value;
- fee -= output.m_fee;
- long_term_fee -= output.m_long_term_fee;
- return m_outputs.erase(it);
}
bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const
@@ -334,35 +338,3 @@ bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_f
&& m_ancestors <= eligibility_filter.max_ancestors
&& m_descendants <= eligibility_filter.max_descendants;
}
-
-void OutputGroup::SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate)
-{
- fee = 0;
- long_term_fee = 0;
- effective_value = 0;
- for (CInputCoin& coin : m_outputs) {
- coin.m_fee = coin.m_input_bytes < 0 ? 0 : effective_feerate.GetFee(coin.m_input_bytes);
- fee += coin.m_fee;
-
- coin.m_long_term_fee = coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes);
- long_term_fee += coin.m_long_term_fee;
-
- coin.effective_value = coin.txout.nValue - coin.m_fee;
- effective_value += coin.effective_value;
- }
-}
-
-OutputGroup OutputGroup::GetPositiveOnlyGroup()
-{
- OutputGroup group(*this);
- for (auto it = group.m_outputs.begin(); it != group.m_outputs.end(); ) {
- const CInputCoin& coin = *it;
- // Only include outputs that are positive effective value (i.e. not dust)
- if (coin.effective_value <= 0) {
- it = group.Discard(coin);
- } else {
- ++it;
- }
- }
- return group;
-}
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index cf9768b927..f0e1addaf1 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -6,11 +6,10 @@
#define BITCOIN_WALLET_COINSELECTION_H
#include <amount.h>
+#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <random.h>
-class CFeeRate;
-
//! target minimum change amount
static constexpr CAmount MIN_CHANGE{COIN / 100};
//! final minimum change amount after paying for fees
@@ -63,9 +62,11 @@ struct CoinEligibilityFilter
const int conf_theirs;
const uint64_t max_ancestors;
const uint64_t max_descendants;
+ const bool m_include_partial_groups{false}; //! Include partial destination groups when avoid_reuse and there are full groups
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_ancestors) {}
CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants) {}
+ CoinEligibilityFilter(int conf_mine, int conf_theirs, uint64_t max_ancestors, uint64_t max_descendants, bool include_partial) : conf_mine(conf_mine), conf_theirs(conf_theirs), max_ancestors(max_ancestors), max_descendants(max_descendants), m_include_partial_groups(include_partial) {}
};
struct OutputGroup
@@ -78,27 +79,18 @@ struct OutputGroup
size_t m_descendants{0};
CAmount effective_value{0};
CAmount fee{0};
+ CFeeRate m_effective_feerate{0};
CAmount long_term_fee{0};
+ CFeeRate m_long_term_feerate{0};
OutputGroup() {}
- OutputGroup(std::vector<CInputCoin>&& outputs, bool from_me, CAmount value, int depth, size_t ancestors, size_t descendants)
- : m_outputs(std::move(outputs))
- , m_from_me(from_me)
- , m_value(value)
- , m_depth(depth)
- , m_ancestors(ancestors)
- , m_descendants(descendants)
+ OutputGroup(const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate) :
+ m_effective_feerate(effective_feerate),
+ m_long_term_feerate(long_term_feerate)
{}
- OutputGroup(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) : OutputGroup() {
- Insert(output, depth, from_me, ancestors, descendants);
- }
- void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants);
- std::vector<CInputCoin>::iterator Discard(const CInputCoin& output);
- bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
- //! Update the OutputGroup's fee, long_term_fee, and effective_value based on the given feerates
- void SetFees(const CFeeRate effective_feerate, const CFeeRate long_term_feerate);
- OutputGroup GetPositiveOnlyGroup();
+ void Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants, bool positive_only);
+ bool EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const;
};
bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 6b46868d10..8505ddc309 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1740,3 +1740,72 @@ RPCHelpMan importdescriptors()
},
};
}
+
+RPCHelpMan listdescriptors()
+{
+ return RPCHelpMan{
+ "listdescriptors",
+ "\nList descriptors imported into a descriptor-enabled wallet.",
+ {},
+ RPCResult{
+ RPCResult::Type::ARR, "", "Response is an array of descriptor objects",
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "desc", "Descriptor string representation"},
+ {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
+ {RPCResult::Type::BOOL, "active", "Activeness flag"},
+ {RPCResult::Type::BOOL, "internal", true, "Whether this is internal or external descriptor; defined only for active descriptors"},
+ {RPCResult::Type::ARR_FIXED, "range", true, "Defined only for ranged descriptors", {
+ {RPCResult::Type::NUM, "", "Range start inclusive"},
+ {RPCResult::Type::NUM, "", "Range end inclusive"},
+ }},
+ {RPCResult::Type::NUM, "next", true, "The next index to generate addresses from; defined only for ranged descriptors"},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+
+ if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
+ }
+
+ LOCK(wallet->cs_wallet);
+
+ UniValue response(UniValue::VARR);
+ const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
+ for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
+ const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
+ if (!desc_spk_man) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
+ }
+ UniValue spk(UniValue::VOBJ);
+ LOCK(desc_spk_man->cs_desc_man);
+ const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
+ spk.pushKV("desc", wallet_descriptor.descriptor->ToString());
+ spk.pushKV("timestamp", wallet_descriptor.creation_time);
+ const bool active = active_spk_mans.count(desc_spk_man) != 0;
+ spk.pushKV("active", active);
+ const auto& type = wallet_descriptor.descriptor->GetOutputType();
+ if (active && type != nullopt) {
+ spk.pushKV("internal", wallet->GetScriptPubKeyMan(*type, true) == desc_spk_man);
+ }
+ if (wallet_descriptor.descriptor->IsRange()) {
+ UniValue range(UniValue::VARR);
+ range.push_back(wallet_descriptor.range_start);
+ range.push_back(wallet_descriptor.range_end - 1);
+ spk.pushKV("range", range);
+ spk.pushKV("next", wallet_descriptor.next_index);
+ }
+ response.push_back(spk);
+ }
+
+ return response;
+},
+ };
+}
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index b865130642..46de273d63 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2615,7 +2615,18 @@ static RPCHelpMan loadwallet()
if (!wallet) {
// Map bad format to not found, since bad format is returned when the
// wallet directory exists, but doesn't contain a data file.
- RPCErrorCode code = status == DatabaseStatus::FAILED_NOT_FOUND || status == DatabaseStatus::FAILED_BAD_FORMAT ? RPC_WALLET_NOT_FOUND : RPC_WALLET_ERROR;
+ RPCErrorCode code = RPC_WALLET_ERROR;
+ switch (status) {
+ case DatabaseStatus::FAILED_NOT_FOUND:
+ case DatabaseStatus::FAILED_BAD_FORMAT:
+ code = RPC_WALLET_NOT_FOUND;
+ break;
+ case DatabaseStatus::FAILED_ALREADY_LOADED:
+ code = RPC_WALLET_ALREADY_LOADED;
+ break;
+ default: // RPC_WALLET_ERROR is returned for all other cases.
+ break;
+ }
throw JSONRPCError(code, error.original);
}
@@ -4540,73 +4551,75 @@ RPCHelpMan importprunedfunds();
RPCHelpMan removeprunedfunds();
RPCHelpMan importmulti();
RPCHelpMan importdescriptors();
+RPCHelpMan listdescriptors();
Span<const CRPCCommand> GetWalletRPCCommands()
{
// clang-format off
static const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // --------------------- ------------------------ ----------------------- ----------
- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
- { "wallet", "abandontransaction", &abandontransaction, {"txid"} },
- { "wallet", "abortrescan", &abortrescan, {} },
- { "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
- { "wallet", "backupwallet", &backupwallet, {"destination"} },
- { "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "psbtbumpfee", &psbtbumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse", "descriptors", "load_on_startup"} },
- { "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
- { "wallet", "dumpwallet", &dumpwallet, {"filename"} },
- { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
- { "wallet", "getaddressesbylabel", &getaddressesbylabel, {"label"} },
- { "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
- { "wallet", "getbalance", &getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} },
- { "wallet", "getnewaddress", &getnewaddress, {"label","address_type"} },
- { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
- { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
- { "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
- { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly","verbose"} },
- { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
- { "wallet", "getbalances", &getbalances, {} },
- { "wallet", "getwalletinfo", &getwalletinfo, {} },
- { "wallet", "importaddress", &importaddress, {"address","label","rescan","p2sh"} },
- { "wallet", "importdescriptors", &importdescriptors, {"requests"} },
- { "wallet", "importmulti", &importmulti, {"requests","options"} },
- { "wallet", "importprivkey", &importprivkey, {"privkey","label","rescan"} },
- { "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
- { "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} },
- { "wallet", "importwallet", &importwallet, {"filename"} },
- { "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
- { "wallet", "listaddressgroupings", &listaddressgroupings, {} },
- { "wallet", "listlabels", &listlabels, {"purpose"} },
- { "wallet", "listlockunspent", &listlockunspent, {} },
- { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
- { "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
- { "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
- { "wallet", "listtransactions", &listtransactions, {"label|dummy","count","skip","include_watchonly"} },
- { "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
- { "wallet", "listwalletdir", &listwalletdir, {} },
- { "wallet", "listwallets", &listwallets, {} },
- { "wallet", "loadwallet", &loadwallet, {"filename", "load_on_startup"} },
- { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
- { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
- { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
- { "wallet", "send", &send, {"outputs","conf_target","estimate_mode","fee_rate","options"} },
- { "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode","fee_rate","verbose"} },
- { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse","fee_rate","verbose"} },
- { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
- { "wallet", "setlabel", &setlabel, {"address","label"} },
- { "wallet", "settxfee", &settxfee, {"amount"} },
- { "wallet", "setwalletflag", &setwalletflag, {"flag","value"} },
- { "wallet", "signmessage", &signmessage, {"address","message"} },
- { "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
- { "wallet", "unloadwallet", &unloadwallet, {"wallet_name", "load_on_startup"} },
- { "wallet", "upgradewallet", &upgradewallet, {"version"} },
- { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
- { "wallet", "walletlock", &walletlock, {} },
- { "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
- { "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
- { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
+{ // category actor (function)
+ // ------------------ ------------------------
+ { "rawtransactions", &fundrawtransaction, },
+ { "wallet", &abandontransaction, },
+ { "wallet", &abortrescan, },
+ { "wallet", &addmultisigaddress, },
+ { "wallet", &backupwallet, },
+ { "wallet", &bumpfee, },
+ { "wallet", &psbtbumpfee, },
+ { "wallet", &createwallet, },
+ { "wallet", &dumpprivkey, },
+ { "wallet", &dumpwallet, },
+ { "wallet", &encryptwallet, },
+ { "wallet", &getaddressesbylabel, },
+ { "wallet", &getaddressinfo, },
+ { "wallet", &getbalance, },
+ { "wallet", &getnewaddress, },
+ { "wallet", &getrawchangeaddress, },
+ { "wallet", &getreceivedbyaddress, },
+ { "wallet", &getreceivedbylabel, },
+ { "wallet", &gettransaction, },
+ { "wallet", &getunconfirmedbalance, },
+ { "wallet", &getbalances, },
+ { "wallet", &getwalletinfo, },
+ { "wallet", &importaddress, },
+ { "wallet", &importdescriptors, },
+ { "wallet", &importmulti, },
+ { "wallet", &importprivkey, },
+ { "wallet", &importprunedfunds, },
+ { "wallet", &importpubkey, },
+ { "wallet", &importwallet, },
+ { "wallet", &keypoolrefill, },
+ { "wallet", &listaddressgroupings, },
+ { "wallet", &listdescriptors, },
+ { "wallet", &listlabels, },
+ { "wallet", &listlockunspent, },
+ { "wallet", &listreceivedbyaddress, },
+ { "wallet", &listreceivedbylabel, },
+ { "wallet", &listsinceblock, },
+ { "wallet", &listtransactions, },
+ { "wallet", &listunspent, },
+ { "wallet", &listwalletdir, },
+ { "wallet", &listwallets, },
+ { "wallet", &loadwallet, },
+ { "wallet", &lockunspent, },
+ { "wallet", &removeprunedfunds, },
+ { "wallet", &rescanblockchain, },
+ { "wallet", &send, },
+ { "wallet", &sendmany, },
+ { "wallet", &sendtoaddress, },
+ { "wallet", &sethdseed, },
+ { "wallet", &setlabel, },
+ { "wallet", &settxfee, },
+ { "wallet", &setwalletflag, },
+ { "wallet", &signmessage, },
+ { "wallet", &signrawtransactionwithwallet, },
+ { "wallet", &unloadwallet, },
+ { "wallet", &upgradewallet, },
+ { "wallet", &walletcreatefundedpsbt, },
+ { "wallet", &walletlock, },
+ { "wallet", &walletpassphrase, },
+ { "wallet", &walletpassphrasechange, },
+ { "wallet", &walletprocesspsbt, },
};
// clang-format on
return MakeSpan(commands);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 202804c9ff..ffac78d752 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -35,7 +35,7 @@ static CAmount balance = 0;
CoinEligibilityFilter filter_standard(1, 6, 0);
CoinEligibilityFilter filter_confirmed(1, 1, 0);
CoinEligibilityFilter filter_standard_extra(6, 6, 0);
-CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0);
+CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(0), 0, false);
static void add_coin(const CAmount& nValue, int nInput, std::vector<CInputCoin>& set)
{
@@ -115,7 +115,10 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<CInputCoin>& coins
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
- for (auto& coin : coins) static_groups.emplace_back(coin, 0, true, 0, 0);
+ for (auto& coin : coins) {
+ static_groups.emplace_back();
+ static_groups.back().Insert(coin, 0, true, 0, 0, false);
+ }
return static_groups;
}
@@ -123,7 +126,10 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
{
static std::vector<OutputGroup> static_groups;
static_groups.clear();
- for (auto& coin : coins) static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0);
+ for (auto& coin : coins) {
+ static_groups.emplace_back();
+ static_groups.back().Insert(coin.GetInputCoin(), coin.nDepth, coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == 1 /* HACK: we can't figure out the is_me flag so we use the conditions defined above; perhaps set safe to false for !fIsFromMe in add_coin() */, 0, 0, false);
+ }
return static_groups;
}
@@ -263,22 +269,22 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
}
// Make sure that effective value is working in SelectCoinsMinConf when BnB is used
- CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0);
+ CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000), 0, false);
CoinSet setCoinsRet;
CAmount nValueRet;
bool bnb_used;
empty_wallet();
add_coin(1);
vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
// Test fees subtracted from output:
empty_wallet();
add_coin(1 * CENT);
vCoins.at(0).nInputBytes = 40;
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
coin_selection_params_bnb.m_subtract_fee_outputs = true;
- BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// Make sure that can use BnB when there are preset inputs
@@ -317,24 +323,24 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
empty_wallet();
// with an empty wallet we can't even pay one cent
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
add_coin(1*CENT, 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// but we can find a new 1 cent
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
add_coin(2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
- BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// we can make 3 cents of new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
add_coin(5*CENT); // add a mature 5 cent coin,
@@ -344,33 +350,33 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// we can't even make 37 cents if we don't allow new coins even if they're from us
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(38 * CENT, filter_standard_extra, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// but we can make 37 cents if we accept new coins from ourself
- BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(37 * CENT, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
// and we can make 38 cents if we accept all new coins
- BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(38 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
- BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(34 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(nValueRet == 8 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
- BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -384,30 +390,30 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
- BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(71 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(!testWallet.SelectCoinsMinConf(72 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
- BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(16 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
- BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(11 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -416,11 +422,11 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin( 2*COIN);
add_coin( 3*COIN);
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
- BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(95 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(195 * CENT, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -435,14 +441,14 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
// we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
- BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
// but if we add a bigger coin, small change is avoided
add_coin(1111*MIN_CHANGE);
// try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// if we add more small coins:
@@ -450,7 +456,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 7 / 10);
// and try again to make 1.0 * MIN_CHANGE
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// run the 'mtgox' test (see https://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
@@ -459,7 +465,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int j = 0; j < 20; j++)
add_coin(50000 * COIN);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(500000 * COIN, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
@@ -472,7 +478,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 7 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(1 * MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
@@ -482,7 +488,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 6 / 10);
add_coin(MIN_CHANGE * 8 / 10);
add_coin(1111 * MIN_CHANGE);
- BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK( testWallet.SelectCoinsMinConf(MIN_CHANGE, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
@@ -493,12 +499,12 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
add_coin(MIN_CHANGE * 100);
// trying to make 100.01 from these three coins
- BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 10001 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE * 10105 / 100); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
- BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(MIN_CHANGE * 9990 / 100, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
}
@@ -512,7 +518,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
// We only create the wallet once to save time, but we still run the coin selection RUN_TESTS times.
for (int i = 0; i < RUN_TESTS; i++) {
- BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(2000, filter_confirmed, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
if (amt - 2000 < MIN_CHANGE) {
// needs more than one input:
@@ -538,8 +544,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
for (int i = 0; i < RUN_TESTS; i++) {
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
- BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(50 * COIN, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
int fails = 0;
@@ -547,8 +553,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(COIN, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
@@ -570,8 +576,8 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
- BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet , nValueRet, coin_selection_params, bnb_used));
- BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet , nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(90*CENT, filter_standard, vCoins, setCoinsRet2, nValueRet, coin_selection_params, bnb_used));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
@@ -598,7 +604,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
add_coin(1000 * COIN);
add_coin(3 * COIN);
- BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(1003 * COIN, filter_standard, vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
@@ -633,13 +639,13 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CAmount target = rand.randrange(balance - 1000) + 1000;
// Perform selection
- CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0);
- CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0);
+ CoinSelectionParams coin_selection_params_knapsack(false, 34, 148, CFeeRate(0), 0, false);
+ CoinSelectionParams coin_selection_params_bnb(true, 34, 148, CFeeRate(0), 0, false);
CoinSet out_set;
CAmount out_value = 0;
bool bnb_used = false;
- BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_bnb, bnb_used) ||
- testWallet.SelectCoinsMinConf(target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_knapsack, bnb_used));
+ BOOST_CHECK(testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_bnb, bnb_used) ||
+ testWallet.SelectCoinsMinConf(target, filter_standard, vCoins, out_set, out_value, coin_selection_params_knapsack, bnb_used));
BOOST_CHECK_GE(out_value, target);
}
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 723552860a..db80745db0 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2357,13 +2357,12 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
return ptx->vout[n];
}
-bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const
{
setCoinsRet.clear();
nValueRet = 0;
- std::vector<OutputGroup> utxo_pool;
if (coin_selection_params.use_bnb) {
// Get long term estimate
FeeCalculation feeCalc;
@@ -2371,35 +2370,27 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
temp.m_confirm_target = 1008;
CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp, &feeCalc);
- // Calculate cost of change
- CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
+ // Get the feerate for effective value.
+ // When subtracting the fee from the outputs, we want the effective feerate to be 0
+ CFeeRate effective_feerate{0};
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ effective_feerate = coin_selection_params.effective_fee;
+ }
- // Filter by the min conf specs and add to utxo_pool and calculate effective value
- for (OutputGroup& group : groups) {
- if (!group.EligibleForSpending(eligibility_filter)) continue;
+ std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, effective_feerate, long_term_feerate, eligibility_filter, true /* positive_only */);
- if (coin_selection_params.m_subtract_fee_outputs) {
- // Set the effective feerate to 0 as we don't want to use the effective value since the fees will be deducted from the output
- group.SetFees(CFeeRate(0) /* effective_feerate */, long_term_feerate);
- } else {
- group.SetFees(coin_selection_params.effective_fee, long_term_feerate);
- }
+ // Calculate cost of change
+ CAmount cost_of_change = GetDiscardRate(*this).GetFee(coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee(coin_selection_params.change_output_size);
- OutputGroup pos_group = group.GetPositiveOnlyGroup();
- if (pos_group.effective_value > 0) utxo_pool.push_back(pos_group);
- }
// Calculate the fees for things that aren't inputs
CAmount not_input_fees = coin_selection_params.effective_fee.GetFee(coin_selection_params.tx_noinputs_size);
bnb_used = true;
- return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
+ return SelectCoinsBnB(groups, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees);
} else {
- // Filter by the min conf specs and add to utxo_pool
- for (const OutputGroup& group : groups) {
- if (!group.EligibleForSpending(eligibility_filter)) continue;
- utxo_pool.push_back(group);
- }
+ std::vector<OutputGroup> groups = GroupOutputs(coins, !coin_selection_params.m_avoid_partial_spends, CFeeRate(0), CFeeRate(0), eligibility_filter, false /* positive_only */);
+
bnb_used = false;
- return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
+ return KnapsackSolver(nTargetValue, groups, setCoinsRet, nValueRet);
}
}
@@ -2482,16 +2473,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// explicitly shuffling the outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
- std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
-
bool res = value_to_select <= 0 ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
- (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) ||
+ (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), vCoins, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// because SelectCoinsMinConf clears the setCoinsRet, we now add the possible inputs to the coinset
util::insert(setCoinsRet, setPresetCoins);
@@ -2782,6 +2771,7 @@ bool CWallet::CreateTransactionInternal(
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
+ coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
// Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservedest so
@@ -4192,51 +4182,90 @@ bool CWalletTx::IsImmatureCoinBase() const
return GetBlocksToMaturity() > 0;
}
-std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const {
- std::vector<OutputGroup> groups;
- std::map<CTxDestination, OutputGroup> gmap;
- std::set<CTxDestination> full_groups;
+std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const
+{
+ std::vector<OutputGroup> groups_out;
- for (const auto& output : outputs) {
- if (output.fSpendable) {
- CTxDestination dst;
- CInputCoin input_coin = output.GetInputCoin();
+ if (separate_coins) {
+ // Single coin means no grouping. Each COutput gets its own OutputGroup.
+ for (const COutput& output : outputs) {
+ // Skip outputs we cannot spend
+ if (!output.fSpendable) continue;
size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
- if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) {
- auto it = gmap.find(dst);
- if (it != gmap.end()) {
- // Limit output groups to no more than OUTPUT_GROUP_MAX_ENTRIES
- // number of entries, to protect against inadvertently creating
- // a too-large transaction when using -avoidpartialspends to
- // prevent breaking consensus or surprising users with a very
- // high amount of fees.
- if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
- groups.push_back(it->second);
- it->second = OutputGroup{};
- full_groups.insert(dst);
- }
- it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- } else {
- gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- }
- } else {
- groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants);
- }
+ CInputCoin input_coin = output.GetInputCoin();
+
+ // Make an OutputGroup containing just this output
+ OutputGroup group{effective_feerate, long_term_feerate};
+ group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
+
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
+ if (positive_only && group.effective_value <= 0) continue;
+ if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
+ }
+ return groups_out;
+ }
+
+ // We want to combine COutputs that have the same scriptPubKey into single OutputGroups
+ // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup.
+ // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups.
+ // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added
+ // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has
+ // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector.
+ std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map;
+ for (const auto& output : outputs) {
+ // Skip outputs we cannot spend
+ if (!output.fSpendable) continue;
+
+ size_t ancestors, descendants;
+ chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
+ CInputCoin input_coin = output.GetInputCoin();
+ CScript spk = input_coin.txout.scriptPubKey;
+
+ std::vector<OutputGroup>& groups = spk_to_groups_map[spk];
+
+ if (groups.size() == 0) {
+ // No OutputGroups for this scriptPubKey yet, add one
+ groups.emplace_back(effective_feerate, long_term_feerate);
}
+
+ // Get the last OutputGroup in the vector so that we can add the CInputCoin to it
+ // A pointer is used here so that group can be reassigned later if it is full.
+ OutputGroup* group = &groups.back();
+
+ // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends
+ // to avoid surprising users with very high fees.
+ if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
+ // The last output group is full, add a new group to the vector and use that group for the insertion
+ groups.emplace_back(effective_feerate, long_term_feerate);
+ group = &groups.back();
+ }
+
+ // Add the input_coin to group
+ group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
}
- if (!single_coin) {
- for (auto& it : gmap) {
- auto& group = it.second;
- if (full_groups.count(it.first) > 0) {
- // Make this unattractive as we want coin selection to avoid it if possible
- group.m_ancestors = max_ancestors - 1;
+
+ // Now we go through the entire map and pull out the OutputGroups
+ for (const auto& spk_and_groups_pair: spk_to_groups_map) {
+ const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second;
+
+ // Go through the vector backwards. This allows for the first item we deal with being the partial group.
+ for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) {
+ const OutputGroup& group = *group_it;
+
+ // Don't include partial groups if there are full groups too and we don't want partial groups
+ if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) {
+ continue;
}
- groups.push_back(group);
+
+ // Check the OutputGroup's eligibility. Only add the eligible ones.
+ if (positive_only && group.effective_value <= 0) continue;
+ if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group);
}
}
- return groups;
+
+ return groups_out;
}
bool CWallet::IsCrypted() const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index e3eae1dd95..4fc4466604 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -610,8 +610,16 @@ struct CoinSelectionParams
size_t tx_noinputs_size = 0;
//! Indicate that we are subtracting the fee from outputs
bool m_subtract_fee_outputs = false;
-
- CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size) : use_bnb(use_bnb), change_output_size(change_output_size), change_spend_size(change_spend_size), effective_fee(effective_fee), tx_noinputs_size(tx_noinputs_size) {}
+ bool m_avoid_partial_spends = false;
+
+ CoinSelectionParams(bool use_bnb, size_t change_output_size, size_t change_spend_size, CFeeRate effective_fee, size_t tx_noinputs_size, bool avoid_partial) :
+ use_bnb(use_bnb),
+ change_output_size(change_output_size),
+ change_spend_size(change_spend_size),
+ effective_fee(effective_fee),
+ tx_noinputs_size(tx_noinputs_size),
+ m_avoid_partial_spends(avoid_partial)
+ {}
CoinSelectionParams() {}
};
@@ -818,7 +826,7 @@ public:
* completion the coin set and corresponding actual target value is
* assembled
*/
- bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups,
+ bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const;
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -827,7 +835,7 @@ public:
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin, const size_t max_ancestors) const;
+ std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const;
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
diff --git a/src/zmq/zmqrpc.cpp b/src/zmq/zmqrpc.cpp
index 1dd751b493..81859f924f 100644
--- a/src/zmq/zmqrpc.cpp
+++ b/src/zmq/zmqrpc.cpp
@@ -52,9 +52,9 @@ static RPCHelpMan getzmqnotifications()
}
const CRPCCommand commands[] =
-{ // category name actor (function) argNames
- // ----------------- ------------------------ ----------------------- ----------
- { "zmq", "getzmqnotifications", &getzmqnotifications, {} },
+{ // category actor (function)
+ // ----------------- -----------------------
+ { "zmq", &getzmqnotifications, },
};
} // anonymous namespace