@@ -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+
211221bool
212222OverlayManagerImpl::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
353370OverlayManagerImpl::~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
399425void
400426OverlayManagerImpl::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
425460void
426461OverlayManagerImpl::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
10361092void
@@ -1067,10 +1123,30 @@ OverlayManagerImpl::maybeAddInboundConnection(Peer::pointer peer)
10671123bool
10681124OverlayManagerImpl::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
10761152bool
@@ -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+
12731405void
12741406OverlayManagerImpl::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
12811419static xdr::opaque_array<32 > const TX_BATCH_HASH = [] {
0 commit comments