docs: public static int getMostCommonChainHeight(final List<Peer> peers) {
          if (peers.isEmpty())
              return 0;
          List<Integer> heights = new ArrayList<>(peers.size());
          for (Peer peer : peers) heights.add((int) peer.getBestHeight());
          return Utils.maxOfMostFreq(heights);
      }
  
      /**
       * Given a list of Peers, return a Peer to be used as the download peer. If you don't want PeerGroup to manage
       * download peer statuses for you, just override this and always return null.
       */
      @Nullable
      protected Peer selectDownloadPeer(List<Peer> peers) {
          // Characteristics to select for in order of importance:
          //  - Chain height is reasonable (majority of nodes)
          //  - High enough protocol version for the features we want (but we'll settle for less)
          //  - Randomly, to try and spread the load.
          if (peers.isEmpty())
              return null;
          // Make sure we don't select a peer that is behind/synchronizing itself.
          int mostCommonChainHeight = getMostCommonChainHeight(peers);
          List<Peer> candidates = new ArrayList<>();
          for (Peer peer : peers) {
              if (!peer.getPeerVersionMessage().hasBlockChain())
                  continue;
              if (peer.getBestHeight() < mostCommonChainHeight)
                  continue;
              candidates.add(peer);
          }
          // Of the candidates, find the peers that meet the minimum protocol version we want to target. We could select
          // the highest version we've seen on the assumption that newer versions are always better but we don't want to
          // zap peers if they upgrade early. If we can't find any peers that have our preferred protocol version or
          // better then we'll settle for the highest we found instead.
          int highestVersion = 0, preferredVersion = 0;
          // If/when PREFERRED_VERSION is not equal to vMinRequiredProtocolVersion, reenable the last test in PeerGroupTest.downloadPeerSelection
          final int PREFERRED_VERSION = params.getProtocolVersionNum(NetworkParameters.ProtocolVersion.BLOOM_FILTER);
          for (Peer peer : candidates) {
              highestVersion = Math.max(peer.getPeerVersionMessage().clientVersion, highestVersion);
              preferredVersion = Math.min(highestVersion, PREFERRED_VERSION);
          }
          ArrayList<Peer> candidates2 = new ArrayList<>(candidates.size());
          for (Peer peer : candidates) {
              if (peer.getPeerVersionMessage().clientVersion >= preferredVersion) {
                  candidates2.add(peer);
              }
          }
          if (candidates2.isEmpty())
              return null;
          int index = (int) (Math.random() * candidates2.size());
          return candidates2.get(index);
      }
  
      /**
       * Returns the currently selected download peer. Bear in mind that it may have changed as soon as this method
       * returns. Can return null if no peer was selected.
       */
      public Peer getDownloadPeer() {
          lock.lock();
          try {
              return downloadPeer;
          } finally {
              lock.unlock();
          }
      }
  
      /**
       * Returns the maximum number of {@link Peer}s to discover. This maximum is checked after
       * each {@link PeerDiscovery} so this max number can be surpassed.
       * @return the maximum number of peers to discover
       */
      public int getMaxPeersToDiscoverCount() {
          return vMaxPeersToDiscoverCount;
      }
  
      /**
       * Sets the maximum number of {@link Peer}s to discover. This maximum is checked after
       * each {@link PeerDiscovery} so this max number can be surpassed.
       * @param maxPeersToDiscoverCount the maximum number of peers to discover
       */
      public void setMaxPeersToDiscoverCount(int maxPeersToDiscoverCount) {
          this.vMaxPeersToDiscoverCount = maxPeersToDiscoverCount;
      }
  
      /** See {@link #setUseLocalhostPeerWhenPossible(boolean)} */
      public boolean getUseLocalhostPeerWhenPossible() {
          lock.lock();
          try {
              return useLocalhostPeerWhenPossible;
          } finally {
              lock.unlock();
          }
      }
  
      /**
       * When true (the default), PeerGroup will attempt to connect to a Bitcoin node running on localhost before
       * attempting to use the P2P network. If successful, only localhost will be used. This makes for a simple
       * and easy way for a user to upgrade a bitcoinj based app running in SPV mode to fully validating security.
       */
      public void setUseLocalhostPeerWhenPossible(boolean useLocalhostPeerWhenPossible) {
          lock.lock();
          try {
              this.useLocalhostPeerWhenPossible = useLocalhostPeerWhenPossible;
          } finally {
              lock.unlock();
          }
      }
  
      public boolean isRunning() {
          return vRunning;
      }
  
      /**
       * Can be used to disable Bloom filtering entirely, even in SPV mode. You are very unlikely to need this, it is
       * an optimisation for rare cases when full validation is not required but it's still more efficient to download
       * full blocks than filtered blocks.
       */
      public void setBloomFilteringEnabled(boolean bloomFilteringEnabled) {
          this.vBloomFilteringEnabled = bloomFilteringEnabled;
      }
  
      /** Returns whether the Bloom filtering protocol optimisation is in use: defaults to true. */
      public boolean isBloomFilteringEnabled() {
          return vBloomFilteringEnabled;
      }