Skip to content

Commit 10ddf62

Browse files
Merge dashpay#5869: backport: Merge bitcoin#18948,19847, 20138, 20561, 19776, 19858,
6c07c2c Merge bitcoin#19858: Periodically make block-relay connections and sync headers (MarcoFalke) 5e23f35 Merge bitcoin#20561: p2p: periodically clear m_addr_known (Wladimir J. van der Laan) 6abbbe1 Merge bitcoin#20138: net: Assume that SetCommonVersion is called at most once per peer (MarcoFalke) 5154fa0 Merge bitcoin#19847: rpc, refactor: Avoid duplicate set lookup in gettxoutproof (Jonas Schnelli) ea82816 Merge bitcoin#18948: qt: Call setParent() in the parent's context (Jonas Schnelli) Pull request description: Bitcoin Backports Top commit has no ACKs. Tree-SHA512: 369ce49a510b77d05aeeb50612ff827b0c994a1511ff72dadeb71f6a115a9624b0d08292119ba8387b71e4ba7b679c6ce23aba03ac8f849f68ed93f6d061f29e
2 parents 132aded + 6c07c2c commit 10ddf62

File tree

11 files changed

+176
-38
lines changed

11 files changed

+176
-38
lines changed

src/init.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ void PrepareShutdown(NodeContext& node)
248248
// using the other before destroying them.
249249
if (node.peerman) UnregisterValidationInterface(node.peerman.get());
250250
// Follow the lock order requirements:
251-
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling GetExtraOutboundCount
251+
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling GetExtraFullOutboundCount
252252
// which locks cs_vNodes.
253253
// * ProcessMessage locks cs_main and g_cs_orphans before indirectly calling ForEachNode which
254254
// locks cs_vNodes.

src/net.cpp

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,22 +2235,36 @@ void CConnman::SetTryNewOutboundPeer(bool flag)
22352235
// Also exclude peers that haven't finished initial connection handshake yet
22362236
// (so that we don't decide we're over our desired connection limit, and then
22372237
// evict some peer that has finished the handshake)
2238-
int CConnman::GetExtraOutboundCount()
2238+
int CConnman::GetExtraFullOutboundCount()
22392239
{
2240-
int nOutbound = 0;
2240+
int full_outbound_peers = 0;
22412241
{
22422242
LOCK(cs_vNodes);
22432243
for (const CNode* pnode : vNodes) {
22442244
// don't count outbound masternodes
22452245
if (pnode->m_masternode_connection) {
22462246
continue;
22472247
}
2248-
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsOutboundOrBlockRelayConn() && !pnode->m_masternode_probe_connection) {
2249-
++nOutbound;
2248+
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsFullOutboundConn() && !pnode->m_masternode_probe_connection) {
2249+
++full_outbound_peers;
22502250
}
22512251
}
22522252
}
2253-
return std::max(nOutbound - m_max_outbound_full_relay - m_max_outbound_block_relay, 0);
2253+
return std::max(full_outbound_peers - m_max_outbound_full_relay, 0);
2254+
}
2255+
2256+
int CConnman::GetExtraBlockRelayCount()
2257+
{
2258+
int block_relay_peers = 0;
2259+
{
2260+
LOCK(cs_vNodes);
2261+
for (const CNode* pnode : vNodes) {
2262+
if (pnode->fSuccessfullyConnected && !pnode->fDisconnect && pnode->IsBlockOnlyConn()) {
2263+
++block_relay_peers;
2264+
}
2265+
}
2266+
}
2267+
return std::max(block_relay_peers - m_max_outbound_block_relay, 0);
22542268
}
22552269

22562270
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
@@ -2282,6 +2296,7 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
22822296

22832297
// Minimum time before next feeler connection (in microseconds).
22842298
int64_t nNextFeeler = PoissonNextSend(nStart*1000*1000, FEELER_INTERVAL);
2299+
int64_t nNextExtraBlockRelay = PoissonNextSend(nStart*1000*1000, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
22852300
while (!interruptNet)
22862301
{
22872302
ProcessAddrFetch();
@@ -2365,8 +2380,9 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
23652380
// until we hit our block-relay-only peer limit.
23662381
// GetTryNewOutboundPeer() gets set when a stale tip is detected, so we
23672382
// try opening an additional OUTBOUND_FULL_RELAY connection. If none of
2368-
// these conditions are met, check the nNextFeeler timer to decide if
2369-
// we should open a FEELER.
2383+
// these conditions are met, check to see if it's time to try an extra
2384+
// block-relay-only peer (to confirm our tip is current, see below) or the nNextFeeler
2385+
// timer to decide if we should open a FEELER.
23702386

23712387
if (!m_anchors.empty() && (nOutboundBlockRelay < m_max_outbound_block_relay)) {
23722388
conn_type = ConnectionType::BLOCK_RELAY;
@@ -2377,6 +2393,30 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect)
23772393
conn_type = ConnectionType::BLOCK_RELAY;
23782394
} else if (GetTryNewOutboundPeer()) {
23792395
// OUTBOUND_FULL_RELAY
2396+
} else if (nTime > nNextExtraBlockRelay && m_start_extra_block_relay_peers) {
2397+
// Periodically connect to a peer (using regular outbound selection
2398+
// methodology from addrman) and stay connected long enough to sync
2399+
// headers, but not much else.
2400+
//
2401+
// Then disconnect the peer, if we haven't learned anything new.
2402+
//
2403+
// The idea is to make eclipse attacks very difficult to pull off,
2404+
// because every few minutes we're finding a new peer to learn headers
2405+
// from.
2406+
//
2407+
// This is similar to the logic for trying extra outbound (full-relay)
2408+
// peers, except:
2409+
// - we do this all the time on a poisson timer, rather than just when
2410+
// our tip is stale
2411+
// - we potentially disconnect our next-youngest block-relay-only peer, if our
2412+
// newest block-relay-only peer delivers a block more recently.
2413+
// See the eviction logic in net_processing.cpp.
2414+
//
2415+
// Because we can promote these connections to block-relay-only
2416+
// connections, they do not get their own ConnectionType enum
2417+
// (similar to how we deal with extra outbound peers).
2418+
nNextExtraBlockRelay = PoissonNextSend(nTime, EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL);
2419+
conn_type = ConnectionType::BLOCK_RELAY;
23802420
} else if (nTime > nNextFeeler) {
23812421
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
23822422
conn_type = ConnectionType::FEELER;

src/net.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@
2929
#include <uint256.h>
3030
#include <util/system.h>
3131
#include <consensus/params.h>
32+
#include <util/check.h>
3233

3334
#include <atomic>
35+
#include <condition_variable>
3436
#include <cstdint>
3537
#include <deque>
3638
#include <map>
37-
#include <thread>
3839
#include <memory>
39-
#include <condition_variable>
40+
#include <thread>
4041
#include <optional>
4142
#include <queue>
4243

@@ -64,6 +65,8 @@ static const int WARNING_INTERVAL = 10 * 60;
6465
static const int FEELER_INTERVAL = 120;
6566
/** The maximum number of entries in an 'inv' protocol message */
6667
static const unsigned int MAX_INV_SZ = 50000;
68+
/** Run the extra block-relay-only connection loop once every 5 minutes. **/
69+
static const int EXTRA_BLOCK_RELAY_ONLY_PEER_INTERVAL = 300;
6770
/** The maximum number of addresses from our addrman to return in response to a getaddr message. */
6871
static constexpr size_t MAX_ADDR_TO_SEND = 1000;
6972
/** Maximum length of incoming protocol messages (no message over 3 MiB is currently acceptable). */
@@ -487,13 +490,20 @@ friend class CNode;
487490
void SetTryNewOutboundPeer(bool flag);
488491
bool GetTryNewOutboundPeer();
489492

493+
void StartExtraBlockRelayPeers() {
494+
LogPrint(BCLog::NET, "net: enabling extra block-relay-only peers\n");
495+
m_start_extra_block_relay_peers = true;
496+
}
497+
490498
// Return the number of outbound peers we have in excess of our target (eg,
491499
// if we previously called SetTryNewOutboundPeer(true), and have since set
492500
// to false, we may have extra peers that we wish to disconnect). This may
493501
// return a value less than (num_outbound_connections - num_outbound_slots)
494502
// in cases where some outbound connections are not yet fully connected, or
495503
// not yet fully disconnected.
496-
int GetExtraOutboundCount();
504+
int GetExtraFullOutboundCount();
505+
// Count the number of block-relay-only peers we have over our limit.
506+
int GetExtraBlockRelayCount();
497507

498508
bool AddNode(const std::string& node);
499509
bool RemoveAddedNode(const std::string& node);
@@ -828,6 +838,12 @@ friend class CNode;
828838
* This takes the place of a feeler connection */
829839
std::atomic_bool m_try_another_outbound_peer;
830840

841+
/** flag for initiating extra block-relay-only peer connections.
842+
* this should only be enabled after initial chain sync has occurred,
843+
* as these connections are intended to be short-lived and low-bandwidth.
844+
*/
845+
std::atomic_bool m_start_extra_block_relay_peers{false};
846+
831847
std::atomic<int64_t> m_next_send_inv_to_incoming{0};
832848

833849
/**
@@ -1417,6 +1433,7 @@ class CNode
14171433

14181434
void SetCommonVersion(int greatest_common_version)
14191435
{
1436+
Assume(m_greatest_common_version == INIT_PROTO_VERSION);
14201437
m_greatest_common_version = greatest_common_version;
14211438
}
14221439
int GetCommonVersion() const

src/net_processing.cpp

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,7 +3097,7 @@ void PeerManagerImpl::ProcessMessage(
30973097
LogPrintf("New outbound peer connected: version: %d, blocks=%d, peer=%d%s (%s)\n",
30983098
pfrom.nVersion.load(), pfrom.nStartingHeight,
30993099
pfrom.GetId(), (fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToString()) : ""),
3100-
pfrom.RelayAddrsWithConn()? "full-relay" : "block-relay");
3100+
pfrom.IsBlockOnlyConn()? "block-relay" : "full-relay");
31013101
}
31023102

31033103
if (!pfrom.m_masternode_probe_connection) {
@@ -4607,11 +4607,54 @@ void PeerManagerImpl::ConsiderEviction(CNode& pto, int64_t time_in_seconds)
46074607

46084608
void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
46094609
{
4610-
// Check whether we have too many outbound peers
4611-
int extra_peers = m_connman.GetExtraOutboundCount();
4612-
if (extra_peers > 0) {
4613-
// If we have more outbound peers than we target, disconnect one.
4614-
// Pick the outbound peer that least recently announced
4610+
// If we have any extra block-relay-only peers, disconnect the youngest unless
4611+
// it's given us a block -- in which case, compare with the second-youngest, and
4612+
// out of those two, disconnect the peer who least recently gave us a block.
4613+
// The youngest block-relay-only peer would be the extra peer we connected
4614+
// to temporarily in order to sync our tip; see net.cpp.
4615+
// Note that we use higher nodeid as a measure for most recent connection.
4616+
if (m_connman.GetExtraBlockRelayCount() > 0) {
4617+
std::pair<NodeId, int64_t> youngest_peer{-1, 0}, next_youngest_peer{-1, 0};
4618+
4619+
m_connman.ForEachNode([&](CNode* pnode) {
4620+
if (!pnode->IsBlockOnlyConn() || pnode->fDisconnect) return;
4621+
if (pnode->GetId() > youngest_peer.first) {
4622+
next_youngest_peer = youngest_peer;
4623+
youngest_peer.first = pnode->GetId();
4624+
youngest_peer.second = pnode->nLastBlockTime;
4625+
}
4626+
});
4627+
NodeId to_disconnect = youngest_peer.first;
4628+
if (youngest_peer.second > next_youngest_peer.second) {
4629+
// Our newest block-relay-only peer gave us a block more recently;
4630+
// disconnect our second youngest.
4631+
to_disconnect = next_youngest_peer.first;
4632+
}
4633+
m_connman.ForNode(to_disconnect, [&](CNode* pnode) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
4634+
AssertLockHeld(::cs_main);
4635+
// Make sure we're not getting a block right now, and that
4636+
// we've been connected long enough for this eviction to happen
4637+
// at all.
4638+
// Note that we only request blocks from a peer if we learn of a
4639+
// valid headers chain with at least as much work as our tip.
4640+
CNodeState *node_state = State(pnode->GetId());
4641+
if (node_state == nullptr ||
4642+
(time_in_seconds - pnode->nTimeConnected >= MINIMUM_CONNECT_TIME && node_state->nBlocksInFlight == 0)) {
4643+
pnode->fDisconnect = true;
4644+
LogPrint(BCLog::NET, "disconnecting extra block-relay-only peer=%d (last block received at time %d)\n", pnode->GetId(), pnode->nLastBlockTime);
4645+
return true;
4646+
} else {
4647+
LogPrint(BCLog::NET, "keeping block-relay-only peer=%d chosen for eviction (connect time: %d, blocks_in_flight: %d)\n",
4648+
pnode->GetId(), pnode->nTimeConnected, node_state->nBlocksInFlight);
4649+
}
4650+
return false;
4651+
});
4652+
}
4653+
4654+
// Check whether we have too many OUTBOUND_FULL_RELAY peers
4655+
if (m_connman.GetExtraFullOutboundCount() > 0) {
4656+
// If we have more OUTBOUND_FULL_RELAY peers than we target, disconnect one.
4657+
// Pick the OUTBOUND_FULL_RELAY peer that least recently announced
46154658
// us a new block, with ties broken by choosing the more recent
46164659
// connection (higher node id)
46174660
NodeId worst_peer = -1;
@@ -4622,14 +4665,13 @@ void PeerManagerImpl::EvictExtraOutboundPeers(int64_t time_in_seconds)
46224665

46234666
// Don't disconnect masternodes just because they were slow in block announcement
46244667
if (pnode->m_masternode_connection) return;
4625-
// Ignore non-outbound peers, or nodes marked for disconnect already
4626-
if (!pnode->IsOutboundOrBlockRelayConn() || pnode->fDisconnect) return;
4668+
// Only consider OUTBOUND_FULL_RELAY peers that are not already
4669+
// marked for disconnection
4670+
if (!pnode->IsFullOutboundConn() || pnode->fDisconnect) return;
46274671
CNodeState *state = State(pnode->GetId());
46284672
if (state == nullptr) return; // shouldn't be possible, but just in case
46294673
// Don't evict our protected peers
46304674
if (state->m_chain_sync.m_protect) return;
4631-
// Don't evict our block-relay-only peers.
4632-
if (!pnode->RelayAddrsWithConn()) return;
46334675
if (state->m_last_block_announcement < oldest_block_announcement || (state->m_last_block_announcement == oldest_block_announcement && pnode->GetId() > worst_peer)) {
46344676
worst_peer = pnode->GetId();
46354677
oldest_block_announcement = state->m_last_block_announcement;
@@ -4685,6 +4727,11 @@ void PeerManagerImpl::CheckForStaleTipAndEvictPeers()
46854727
}
46864728
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
46874729
}
4730+
4731+
if (!m_initial_sync_finished && CanDirectFetch(m_chainparams.GetConsensus())) {
4732+
m_connman.StartExtraBlockRelayPeers();
4733+
m_initial_sync_finished = true;
4734+
}
46884735
}
46894736

46904737
namespace {
@@ -4757,6 +4804,15 @@ bool PeerManagerImpl::SendMessages(CNode* pto)
47574804
if (fListen && pto->RelayAddrsWithConn() &&
47584805
!m_chainman.ActiveChainstate().IsInitialBlockDownload() &&
47594806
pto->m_next_local_addr_send < current_time) {
4807+
// If we've sent before, clear the bloom filter for the peer, so that our
4808+
// self-announcement will actually go out.
4809+
// This might be unnecessary if the bloom filter has already rolled
4810+
// over since our last self-announcement, but there is only a small
4811+
// bandwidth cost that we can incur by doing this (which happens
4812+
// once a day on average).
4813+
if (pto->m_next_local_addr_send != std::chrono::microseconds::zero()) {
4814+
pto->m_addr_known->reset();
4815+
}
47604816
if (std::optional<CAddress> local_addr = GetLocalAddrForPeer(pto)) {
47614817
FastRandomContext insecure_rand;
47624818
pto->PushAddress(*local_addr, insecure_rand);

src/net_processing.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ class PeerManager : public CValidationInterface, public NetEventsInterface
7575
int64_t nTimeReceived, const std::atomic<bool>& interruptMsgProc) = 0;
7676

7777
virtual bool IsBanned(NodeId pnode) = 0;
78+
79+
/** Whether we've completed initial sync yet, for determining when to turn
80+
* on extra block-relay-only peers. */
81+
bool m_initial_sync_finished{false};
82+
7883
};
7984

8085
#endif // BITCOIN_NET_PROCESSING_H

src/qt/guiutil.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,20 @@ namespace GUIUtil
504504
return string.split(separator, QString::SkipEmptyParts);
505505
#endif
506506
}
507+
508+
/**
509+
* Queue a function to run in an object's event loop. This can be
510+
* replaced by a call to the QMetaObject::invokeMethod functor overload after Qt 5.10, but
511+
* for now use a QObject::connect for compatibility with older Qt versions, based on
512+
* https://stackoverflow.com/questions/21646467/how-to-execute-a-functor-or-a-lambda-in-a-given-thread-in-qt-gcd-style
513+
*/
514+
template <typename Fn>
515+
void ObjectInvoke(QObject* object, Fn&& function, Qt::ConnectionType connection = Qt::QueuedConnection)
516+
{
517+
QObject source;
518+
QObject::connect(&source, &QObject::destroyed, object, std::forward<Fn>(function), connection);
519+
}
520+
507521
} // namespace GUIUtil
508522

509523
#endif // BITCOIN_QT_GUIUTIL_H

src/qt/walletcontroller.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,20 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal
129129
}
130130

131131
// Instantiate model and register it.
132-
WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model, nullptr);
133-
// Handler callback runs in a different thread so fix wallet model thread affinity.
132+
WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model,
133+
nullptr /* required for the following moveToThread() call */);
134+
135+
// Move WalletModel object to the thread that created the WalletController
136+
// object (GUI main thread), instead of the current thread, which could be
137+
// an outside wallet thread or RPC thread sending a LoadWallet notification.
138+
// This ensures queued signals sent to the WalletModel object will be
139+
// handled on the GUI event loop.
134140
wallet_model->moveToThread(thread());
135-
wallet_model->setParent(this);
141+
// setParent(parent) must be called in the thread which created the parent object. More details in #18948.
142+
GUIUtil::ObjectInvoke(this, [wallet_model, this] {
143+
wallet_model->setParent(this);
144+
}, GUIUtil::blockingGUIThreadConnection());
145+
136146
m_wallets.push_back(wallet_model);
137147

138148
// WalletModel::startPollBalance needs to be called in a thread managed by
@@ -158,7 +168,6 @@ WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wal
158168
// Re-emit coinsSent signal from wallet model.
159169
connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
160170

161-
// Notify walletAdded signal on the GUI thread.
162171
Q_EMIT walletAdded(wallet_model);
163172

164173
return wallet_model;

src/rpc/rawtransaction.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -591,16 +591,15 @@ static UniValue gettxoutproof(const JSONRPCRequest& request)
591591
}.Check(request);
592592

593593
std::set<uint256> setTxids;
594-
uint256 oneTxid;
595594
UniValue txids = request.params[0].get_array();
595+
if (txids.empty()) {
596+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txids' cannot be empty");
597+
}
596598
for (unsigned int idx = 0; idx < txids.size(); idx++) {
597-
const UniValue& txid = txids[idx];
598-
uint256 hash(ParseHashV(txid, "txid"));
599-
if (setTxids.count(hash)) {
600-
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ")+txid.get_str());
599+
auto ret = setTxids.insert(ParseHashV(txids[idx], "txid"));
600+
if (!ret.second) {
601+
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated txid: ") + txids[idx].get_str());
601602
}
602-
setTxids.insert(hash);
603-
oneTxid = hash;
604603
}
605604

606605
CBlockIndex* pblockindex = nullptr;
@@ -636,7 +635,7 @@ static UniValue gettxoutproof(const JSONRPCRequest& request)
636635
LOCK(cs_main);
637636

638637
if (pblockindex == nullptr) {
639-
const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, oneTxid, Params().GetConsensus(), hashBlock);
638+
const CTransactionRef tx = GetTransaction(/* block_index */ nullptr, /* mempool */ nullptr, *setTxids.begin(), Params().GetConsensus(), hashBlock);
640639
if (!tx || hashBlock.IsNull()) {
641640
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block");
642641
}

src/test/fuzz/connman.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ FUZZ_TARGET_INIT(connman, initialize_connman)
117117
});
118118
}
119119
(void)connman.GetAddedNodeInfo();
120-
(void)connman.GetExtraOutboundCount();
120+
(void)connman.GetExtraFullOutboundCount();
121121
(void)connman.GetLocalServices();
122122
(void)connman.GetMaxOutboundTarget();
123123
(void)connman.GetMaxOutboundTimeframe();

0 commit comments

Comments
 (0)