/**
   * Called when the connection terminates. Notifies the {@link StoredServerChannel} object that we
   * can attempt to resume this channel in the future and stops generating messages for the client.
   *
   * <p>Note that this <b>MUST</b> still be called even after either {@link
   * ServerConnection#destroyConnection(CloseReason)} or {@link PaymentChannelServer#close()} is
   * called to actually handle the connection close logic.
   */
  public void connectionClosed() {
    lock.lock();
    try {
      log.info("Server channel closed.");
      connectionOpen = false;

      try {
        if (state != null && state.getMultisigContract() != null) {
          StoredPaymentChannelServerStates channels =
              (StoredPaymentChannelServerStates)
                  wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
          if (channels != null) {
            StoredServerChannel storedServerChannel =
                channels.getChannel(state.getMultisigContract().getHash());
            if (storedServerChannel != null) {
              storedServerChannel.clearConnectedHandler();
            }
          }
        }
      } catch (IllegalStateException e) {
        // Expected when we call getMultisigContract() sometimes
      }
    } finally {
      lock.unlock();
    }
  }
  @GuardedBy("lock")
  private void receiveVersionMessage(Protos.TwoWayChannelMessage msg) throws VerificationException {
    checkState(step == InitStep.WAITING_ON_CLIENT_VERSION && msg.hasClientVersion());
    final Protos.ClientVersion clientVersion = msg.getClientVersion();
    final int major = clientVersion.getMajor();
    if (major != SERVER_MAJOR_VERSION) {
      error(
          "This server needs protocol version "
              + SERVER_MAJOR_VERSION
              + " , client offered "
              + major,
          Protos.Error.ErrorCode.NO_ACCEPTABLE_VERSION,
          CloseReason.NO_ACCEPTABLE_VERSION);
      return;
    }

    Protos.ServerVersion.Builder versionNegotiationBuilder =
        Protos.ServerVersion.newBuilder()
            .setMajor(SERVER_MAJOR_VERSION)
            .setMinor(SERVER_MINOR_VERSION);
    conn.sendToClient(
        Protos.TwoWayChannelMessage.newBuilder()
            .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION)
            .setServerVersion(versionNegotiationBuilder)
            .build());
    ByteString reopenChannelContractHash = clientVersion.getPreviousChannelContractHash();
    if (reopenChannelContractHash != null && reopenChannelContractHash.size() == 32) {
      Sha256Hash contractHash = new Sha256Hash(reopenChannelContractHash.toByteArray());
      log.info("New client that wants to resume {}", contractHash);
      StoredPaymentChannelServerStates channels =
          (StoredPaymentChannelServerStates)
              wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
      if (channels != null) {
        StoredServerChannel storedServerChannel = channels.getChannel(contractHash);
        if (storedServerChannel != null) {
          final PaymentChannelServer existingHandler =
              storedServerChannel.setConnectedHandler(this, false);
          if (existingHandler != this) {
            log.warn("  ... and that channel is already in use, disconnecting other user.");
            existingHandler.close();
            storedServerChannel.setConnectedHandler(this, true);
          }

          log.info("Got resume version message, responding with VERSIONS and CHANNEL_OPEN");
          state = storedServerChannel.getOrCreateState(wallet, broadcaster);
          step = InitStep.CHANNEL_OPEN;
          conn.sendToClient(
              Protos.TwoWayChannelMessage.newBuilder()
                  .setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
                  .build());
          conn.channelOpen(contractHash);
          return;
        } else {
          log.error(" ... but we do not have any record of that contract! Resume failed.");
        }
      } else {
        log.error(" ... but we do not have any stored channels! Resume failed.");
      }
    }
    log.info(
        "Got initial version message, responding with VERSIONS and INITIATE: min value={}",
        minAcceptedChannelSize.value);

    myKey = new ECKey();
    wallet.freshReceiveKey();

    expireTime = Utils.currentTimeSeconds() + truncateTimeWindow(clientVersion.getTimeWindowSecs());
    step = InitStep.WAITING_ON_UNSIGNED_REFUND;

    Protos.Initiate.Builder initiateBuilder =
        Protos.Initiate.newBuilder()
            .setMultisigKey(ByteString.copyFrom(myKey.getPubKey()))
            .setExpireTimeSecs(expireTime)
            .setMinAcceptedChannelSize(minAcceptedChannelSize.value)
            .setMinPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value);

    conn.sendToClient(
        Protos.TwoWayChannelMessage.newBuilder()
            .setInitiate(initiateBuilder)
            .setType(Protos.TwoWayChannelMessage.MessageType.INITIATE)
            .build());
  }