code: @Override
    public void join(String peer) throws Exception {
      try {
        // Initializes the persistent variables.
        List<String> peers = new ArrayList<String>();
        peers.add(this.serverId);
        ClusterConfiguration cnf =
          new ClusterConfiguration(new Zxid(0, 0), peers, this.serverId);
        persistence.setLastSeenConfig(cnf);
        ByteBuffer cop = cnf.toByteBuffer();
        Transaction txn =
          new Transaction(cnf.getVersion(), ProposalType.COP_VALUE, cop);
        // Also we need to append the initial configuration to log.
        persistence.getLog().append(txn);
        persistence.setProposedEpoch(0);
        persistence.setAckEpoch(0);
  
        /* -- Broadcasting phase -- */
        // Initialize the vote for leader election.
        this.election.specifyLeader(this.serverId);
        changePhase(Phase.BROADCASTING);
        broadcasting();
      } catch (InterruptedException e) {
        LOG.debug("Participant is canceled by user.");
        throw e;
      } catch (TimeoutException e) {
        LOG.debug("Didn't hear message from peers for {} milliseconds. Going"
                  + " back to leader election.",
                  this.config.getTimeoutMs());
      } catch (BackToElectionException e) {
        LOG.debug("Got GO_BACK message from queue, going back to electing.");
      } catch (LeftCluster e) {
        LOG.debug("Exit running : {}", e.getMessage());
        throw e;
      } catch (Exception e) {
        LOG.error("Caught exception", e);
        throw e;
      } finally {
        changePhase(Phase.FINALIZING);
      }
    }
docs: Starts from joining leader itself.
  
  @param peer should be as same as serverId of leader.
  @throws Exception in case something goes wrong.