Skip to content

Commit 3034278

Browse files
committed
Mutual quorum peers do not count towards overlay limits
1 parent 3018d88 commit 3034278

19 files changed

Lines changed: 872 additions & 67 deletions

src/main/AppConnector.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,6 @@ AppConnector::now() const
142142
return mApp.getClock().now();
143143
}
144144

145-
VirtualClock::system_time_point
146-
AppConnector::systemNow() const
147-
{
148-
return mApp.getClock().system_now();
149-
}
150-
151145
bool
152146
AppConnector::shouldYield() const
153147
{

src/main/AppConnector.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ class AppConnector
5959
void postOnEvictionBackgroundThread(std::function<void()>&& f,
6060
std::string const& jobName);
6161
VirtualClock::time_point now() const;
62-
VirtualClock::system_time_point systemNow() const;
6362
Config const& getConfig() const;
6463
rust::Box<rust_bridge::SorobanModuleCache> getModuleCache();
6564
bool overlayShuttingDown() const;

src/overlay/OverlayManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class PeerManager;
5353
class QuorumPeerState;
5454
class SurveyManager;
5555
struct StellarMessage;
56+
enum class RemoteQsetRole : uint32_t;
5657

5758
class OverlayManager
5859
{
@@ -143,6 +144,12 @@ class OverlayManager
143144

144145
virtual bool isPreferred(Peer* peer) const = 0;
145146
virtual bool isDirectQsetPeer(NodeID const& nodeID) const = 0;
147+
// Records the outcome of an authenticated handshake with a direct qset
148+
// peer: updates persisted quorum peer state and promotes or demotes the
149+
// peer's addresses in the peers table based on mutuality.
150+
virtual void recordQsetPeerHandshake(NodeID const& nodeID,
151+
RemoteQsetRole remoteRole,
152+
PeerBareAddress const& address) = 0;
146153
virtual void recordProbedNonQsetAddress(PeerBareAddress const& address) = 0;
147154
virtual bool isPossiblyPreferred(std::string const& ip) const = 0;
148155
virtual bool haveSpaceForConnection(std::string const& ip) const = 0;

src/overlay/OverlayManagerImpl.cpp

Lines changed: 161 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,16 @@ OverlayManagerImpl::PeersList::moveToAuthenticated(Peer::pointer peer)
208208
return true;
209209
}
210210

211+
size_t
212+
OverlayManagerImpl::PeersList::nonQsetAuthenticatedCount() const
213+
{
214+
releaseAssert(threadIsMain());
215+
auto mutualQsetCount = std::count_if(
216+
std::begin(mAuthenticated), std::end(mAuthenticated),
217+
[](auto const& peer) { return peer.second->isMutualQsetPeer(); });
218+
return mAuthenticated.size() - mutualQsetCount;
219+
}
220+
211221
bool
212222
OverlayManagerImpl::PeersList::acceptAuthenticatedPeer(Peer::pointer peer)
213223
{
@@ -223,9 +233,11 @@ OverlayManagerImpl::PeersList::acceptAuthenticatedPeer(Peer::pointer peer)
223233

224234
CLOG_TRACE(Overlay, "Trying to promote peer to authenticated {}",
225235
peer->toString());
236+
// Mutual direct-qset peers do not count against the operator-configured
237+
// limit, so capacity checks compare against the non-qset population only.
226238
if (mOverlayManager.isPreferred(peer.get()))
227239
{
228-
if (mAuthenticated.size() < mMaxAuthenticatedCount)
240+
if (nonQsetAuthenticatedCount() < mMaxAuthenticatedCount)
229241
{
230242
return moveToAuthenticated(peer);
231243
}
@@ -247,7 +259,7 @@ OverlayManagerImpl::PeersList::acceptAuthenticatedPeer(Peer::pointer peer)
247259
}
248260

249261
if (!mOverlayManager.mApp.getConfig().PREFERRED_PEERS_ONLY &&
250-
mAuthenticated.size() < mMaxAuthenticatedCount)
262+
nonQsetAuthenticatedCount() < mMaxAuthenticatedCount)
251263
{
252264
return moveToAuthenticated(peer);
253265
}
@@ -343,11 +355,16 @@ OverlayManagerImpl::OverlayManagerImpl(Application& app)
343355
mPeerSources[PeerType::PREFERRED] = std::make_unique<RandomPeerSource>(
344356
mPeerManager, RandomPeerSource::nextAttemptCutoff(PeerType::PREFERRED));
345357

346-
LocalNode::forAllNodes(mApp.getConfig().QUORUM_SET,
347-
[&](NodeID const& nodeID) {
348-
mDirectQsetPeers.insert(nodeID);
349-
return true;
350-
});
358+
LocalNode::forAllNodes(
359+
mApp.getConfig().QUORUM_SET, [&](NodeID const& nodeID) {
360+
// Validators usually list themselves in their own qset; we never
361+
// connect to ourselves, so keep self out of the peering state.
362+
if (!(nodeID == mApp.getConfig().NODE_SEED.getPublicKey()))
363+
{
364+
mDirectQsetPeers.insert(nodeID);
365+
}
366+
return true;
367+
});
351368
}
352369

353370
OverlayManagerImpl::~OverlayManagerImpl()
@@ -392,13 +409,24 @@ OverlayManagerImpl::reconcileQuorumPeerState()
392409
mQuorumPeerState =
393410
QuorumPeerState::fromJson(mApp.getPersistentState().getState(
394411
PersistentState::kQuorumPeerInfo, session));
395-
mQuorumPeerState.reconcile(mDirectQsetPeers);
412+
auto removed = mQuorumPeerState.reconcile(mDirectQsetPeers);
413+
for (auto const& entry : removed)
414+
{
415+
// Peers no longer in our qset must not keep their aggressive
416+
// reconnection priority.
417+
if (entry.second.address)
418+
{
419+
demoteQsetPeerAddress(entry.first, *entry.second.address);
420+
}
421+
}
396422
persistQuorumPeerState();
397423
}
398424

399425
void
400426
OverlayManagerImpl::seedQuorumPeerAddresses()
401427
{
428+
auto const now = static_cast<uint64_t>(
429+
VirtualClock::to_time_t(mApp.getClock().system_now()));
402430
for (auto const& entry : mQuorumPeerState.getInfo())
403431
{
404432
auto const& info = entry.second;
@@ -407,6 +435,12 @@ OverlayManagerImpl::seedQuorumPeerAddresses()
407435
continue;
408436
}
409437

438+
// Give every persisted address a fresh TTL window after a restart:
439+
// lastConnection is only recorded at handshake time, so without this
440+
// the state of a long-lived pre-restart connection would be expired
441+
// before we get a chance to reconnect.
442+
mQuorumPeerState.refreshLastConnection(entry.first, now);
443+
410444
if (info.remoteRole == RemoteQsetRole::Direct)
411445
{
412446
getPeerManager().update(*info.address, PeerType::PREFERRED,
@@ -420,22 +454,36 @@ OverlayManagerImpl::seedQuorumPeerAddresses()
420454
PeerManager::BackOffUpdate::HARD_RESET);
421455
}
422456
}
457+
persistQuorumPeerState();
423458
}
424459

425460
void
426461
OverlayManagerImpl::expireStaleQuorumPeerAddresses()
427462
{
428463
auto const now = static_cast<uint64_t>(
429464
VirtualClock::to_time_t(mApp.getClock().system_now()));
465+
466+
// A live authenticated connection keeps a qset peer's address fresh;
467+
// lastConnection is otherwise only written at handshake time, and
468+
// connections routinely outlive the TTL.
469+
for (auto const& peer : getAuthenticatedPeers())
470+
{
471+
mQuorumPeerState.refreshLastConnection(peer.first, now);
472+
}
473+
430474
auto expired = mQuorumPeerState.expireStaleAddresses(
431475
now, QUORUM_PEER_STALE_ADDRESS_TTL);
432476
for (auto const& entry : expired)
433477
{
434478
auto const& info = entry.second;
435479
if (info.address)
436480
{
437-
getPeerManager().update(*info.address, PeerType::OUTBOUND,
438-
/* preferredTypeKnown */ true);
481+
CLOG_INFO(Overlay,
482+
"Expiring stale address {} of direct qset peer {}, "
483+
"re-entering discovery",
484+
info.address->toString(),
485+
mApp.getConfig().toShortString(entry.first));
486+
demoteQsetPeerAddress(entry.first, *info.address);
439487
}
440488
}
441489

@@ -697,15 +745,19 @@ OverlayManagerImpl::connectToQsetPeers(int& availablePendingSlots)
697745
}
698746

699747
auto missing = mDirectQsetPeers;
700-
missing.erase(mApp.getConfig().NODE_SEED.getPublicKey());
701748
for (auto const& peer : getAuthenticatedPeers())
702749
{
703750
missing.erase(peer.first);
704751
}
705752
for (auto it = missing.begin(); it != missing.end();)
706753
{
707754
auto info = mQuorumPeerState.getInfo(*it);
708-
if (info && info->remoteRole == RemoteQsetRole::None)
755+
// Discovery probing is only for peers whose address we do not know.
756+
// Peers with a known address are dialed directly through the peers
757+
// table (as PREFERRED when mutual), and peers that told us the
758+
// relationship is not mutual are not chased at all.
759+
if (info &&
760+
(info->remoteRole == RemoteQsetRole::None || info->address))
709761
{
710762
it = missing.erase(it);
711763
}
@@ -743,6 +795,10 @@ OverlayManagerImpl::connectToQsetPeers(int& availablePendingSlots)
743795
continue;
744796
}
745797

798+
CLOG_DEBUG(Overlay,
799+
"Probing {} while searching for {} direct qset peers with "
800+
"unknown addresses",
801+
address.toString(), missing.size());
746802
if (connectToImpl(address, false))
747803
{
748804
--availablePendingSlots;
@@ -973,12 +1029,7 @@ OverlayManagerImpl::availableOutboundAuthenticatedSlots() const
9731029
? OverlayManager::MIN_INBOUND_FACTOR
9741030
: mApp.getConfig().TARGET_PEER_CONNECTIONS;
9751031

976-
auto mutualQsetCount = std::count_if(
977-
std::begin(mOutboundPeers.mAuthenticated),
978-
std::end(mOutboundPeers.mAuthenticated),
979-
[](auto const& peer) { return peer.second->isMutualQsetPeer(); });
980-
auto ordinaryOutboundCount =
981-
mOutboundPeers.mAuthenticated.size() - mutualQsetCount;
1032+
auto ordinaryOutboundCount = mOutboundPeers.nonQsetAuthenticatedCount();
9821033

9831034
if (ordinaryOutboundCount < adjustedTarget)
9841035
{
@@ -1031,6 +1082,11 @@ OverlayManagerImpl::updateSizeCounters()
10311082
mOverlayMetrics.mPendingPeersSize.set_count(getPendingPeersCount());
10321083
mOverlayMetrics.mAuthenticatedPeersSize.set_count(
10331084
getAuthenticatedPeersCount());
1085+
mOverlayMetrics.mMutualQsetPeersSize.set_count(static_cast<int64_t>(
1086+
(mInboundPeers.mAuthenticated.size() -
1087+
mInboundPeers.nonQsetAuthenticatedCount()) +
1088+
(mOutboundPeers.mAuthenticated.size() -
1089+
mOutboundPeers.nonQsetAuthenticatedCount())));
10341090
}
10351091

10361092
void
@@ -1067,10 +1123,30 @@ OverlayManagerImpl::maybeAddInboundConnection(Peer::pointer peer)
10671123
bool
10681124
OverlayManagerImpl::isPossiblyPreferred(std::string const& ip) const
10691125
{
1070-
return std::any_of(
1071-
std::begin(mConfigurationPreferredPeers),
1072-
std::end(mConfigurationPreferredPeers),
1073-
[&](PeerBareAddress const& address) { return address.getIP() == ip; });
1126+
if (std::any_of(
1127+
std::begin(mConfigurationPreferredPeers),
1128+
std::end(mConfigurationPreferredPeers),
1129+
[&](PeerBareAddress const& address)
1130+
{ return address.getIP() == ip; }))
1131+
{
1132+
return true;
1133+
}
1134+
1135+
// An inbound connection from a mutual qset peer must be able to reach the
1136+
// authentication stage even when ordinary pending slots are saturated, so
1137+
// addresses matching known qset peers get the same pending-slot
1138+
// preference as configured preferred peers.
1139+
for (auto const& entry : mQuorumPeerState.getInfo())
1140+
{
1141+
auto const& info = entry.second;
1142+
if (info.remoteRole != RemoteQsetRole::None && info.address &&
1143+
info.address->getIP() == ip)
1144+
{
1145+
return true;
1146+
}
1147+
}
1148+
1149+
return false;
10741150
}
10751151

10761152
bool
@@ -1270,12 +1346,74 @@ OverlayManagerImpl::isDirectQsetPeer(NodeID const& nodeID) const
12701346
return mDirectQsetPeers.count(nodeID) != 0;
12711347
}
12721348

1349+
void
1350+
OverlayManagerImpl::recordQsetPeerHandshake(NodeID const& nodeID,
1351+
RemoteQsetRole remoteRole,
1352+
PeerBareAddress const& address)
1353+
{
1354+
releaseAssert(threadIsMain());
1355+
releaseAssert(isDirectQsetPeer(nodeID));
1356+
releaseAssert(!address.isEmpty());
1357+
1358+
auto const now = static_cast<uint64_t>(
1359+
VirtualClock::to_time_t(mApp.getClock().system_now()));
1360+
auto previousAddress =
1361+
mQuorumPeerState.recordHandshake(nodeID, remoteRole, address, now);
1362+
persistQuorumPeerState();
1363+
1364+
if (remoteRole == RemoteQsetRole::Direct)
1365+
{
1366+
// The peering is mutual: remember the address as preferred so we
1367+
// reconnect aggressively across restarts.
1368+
getPeerManager().update(address, PeerType::PREFERRED,
1369+
/* preferredTypeKnown */ true);
1370+
}
1371+
else if (remoteRole == RemoteQsetRole::None)
1372+
{
1373+
// The peer explicitly told us the relationship is not mutual (e.g.
1374+
// its operator dropped us from its qset); back off gracefully rather
1375+
// than keep dialing it as a preferred peer.
1376+
demoteQsetPeerAddress(nodeID, address);
1377+
}
1378+
1379+
if (previousAddress)
1380+
{
1381+
// The peer moved to a new address; stop chasing the old one.
1382+
CLOG_INFO(Overlay, "Direct qset peer {} moved from {} to {}",
1383+
mApp.getConfig().toShortString(nodeID),
1384+
previousAddress->toString(), address.toString());
1385+
demoteQsetPeerAddress(nodeID, *previousAddress);
1386+
}
1387+
}
1388+
1389+
void
1390+
OverlayManagerImpl::demoteQsetPeerAddress(NodeID const& nodeID,
1391+
PeerBareAddress const& address)
1392+
{
1393+
// Never demote addresses (or keys) the operator explicitly configured as
1394+
// preferred; qset peering only manages the records it created itself.
1395+
if (mConfigurationPreferredPeers.find(address) !=
1396+
mConfigurationPreferredPeers.end() ||
1397+
mApp.getConfig().PREFERRED_PEER_KEYS.count(nodeID) != 0)
1398+
{
1399+
return;
1400+
}
1401+
getPeerManager().update(address, PeerType::OUTBOUND,
1402+
/* preferredTypeKnown */ true);
1403+
}
1404+
12731405
void
12741406
OverlayManagerImpl::recordProbedNonQsetAddress(PeerBareAddress const& address)
12751407
{
12761408
releaseAssert(threadIsMain());
12771409
releaseAssert(!address.isEmpty());
1278-
mProbedNonQset.insert(address);
1410+
// Cap the in-memory book-keeping; beyond the cap the only cost is a
1411+
// redundant probe of an address we have already seen.
1412+
constexpr size_t MAX_PROBED_NON_QSET_ADDRESSES = 10000;
1413+
if (mProbedNonQset.size() < MAX_PROBED_NON_QSET_ADDRESSES)
1414+
{
1415+
mProbedNonQset.insert(address);
1416+
}
12791417
}
12801418

12811419
static xdr::opaque_array<32> const TX_BATCH_HASH = [] {

src/overlay/OverlayManagerImpl.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ class OverlayManagerImpl : public OverlayManager
4545
std::set<NodeID> mDirectQsetPeers;
4646
// Addresses that were proactively probed while searching for direct-qset
4747
// peers and authenticated as non-qset peers. This is intentionally
48-
// in-memory only; next-attempt backoff limits churn until restart.
48+
// in-memory only and bounded in size; next-attempt backoff limits churn
49+
// until restart.
4950
std::set<PeerBareAddress> mProbedNonQset;
5051
QuorumPeerState mQuorumPeerState;
5152

@@ -79,6 +80,9 @@ class OverlayManagerImpl : public OverlayManager
7980
void removePeer(Peer* peer);
8081
bool moveToAuthenticated(Peer::pointer peer);
8182
bool acceptAuthenticatedPeer(Peer::pointer peer);
83+
// Authenticated peers that are not mutual direct-qset peers; only
84+
// these count against the operator-configured connection limits.
85+
size_t nonQsetAuthenticatedCount() const;
8286
void shutdown();
8387
};
8488

@@ -141,6 +145,9 @@ class OverlayManagerImpl : public OverlayManager
141145
bool acceptAuthenticatedPeer(Peer::pointer peer) override;
142146
bool isPreferred(Peer* peer) const override;
143147
bool isDirectQsetPeer(NodeID const& nodeID) const override;
148+
void recordQsetPeerHandshake(NodeID const& nodeID,
149+
RemoteQsetRole remoteRole,
150+
PeerBareAddress const& address) override;
144151
void recordProbedNonQsetAddress(PeerBareAddress const& address) override;
145152
std::vector<Peer::pointer> const& getInboundPendingPeers() const override;
146153
std::vector<Peer::pointer> const& getOutboundPendingPeers() const override;
@@ -214,6 +221,10 @@ class OverlayManagerImpl : public OverlayManager
214221
int connectTo(std::vector<PeerBareAddress> const& peers,
215222
bool forceoutbound);
216223
void connectToQsetPeers(int& availablePendingSlots);
224+
// Drops a qset peer's address back to ordinary OUTBOUND status, unless
225+
// the operator explicitly configured it (or the peer's key) as preferred.
226+
void demoteQsetPeerAddress(NodeID const& nodeID,
227+
PeerBareAddress const& address);
217228
std::vector<PeerBareAddress> getPeersToConnectTo(int maxNum,
218229
PeerType peerType);
219230

0 commit comments

Comments
 (0)