private long truncateTimeWindow(long timeWindow) {
   if (timeWindow < minTimeWindow) {
     log.info(
         "client requested time window {} s to short, offering {} s", timeWindow, minTimeWindow);
     return minTimeWindow;
   }
   if (timeWindow > maxTimeWindow) {
     log.info(
         "client requested time window {} s to long, offering {} s", timeWindow, minTimeWindow);
     return maxTimeWindow;
   }
   return timeWindow;
 }
  @GuardedBy("lock")
  private void receiveUpdatePaymentMessage(Protos.UpdatePayment msg, boolean sendAck)
      throws VerificationException, ValueOutOfRangeException, InsufficientMoneyException {
    log.info("Got a payment update");

    Coin lastBestPayment = state.getBestValueToMe();
    final Coin refundSize = Coin.valueOf(msg.getClientChangeValue());
    boolean stillUsable = state.incrementPayment(refundSize, msg.getSignature().toByteArray());
    Coin bestPaymentChange = state.getBestValueToMe().subtract(lastBestPayment);

    ListenableFuture<ByteString> ackInfoFuture = null;
    if (bestPaymentChange.signum() > 0) {
      ByteString info = (msg.hasInfo()) ? msg.getInfo() : null;
      ackInfoFuture = conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe(), info);
    }

    if (sendAck) {
      final Protos.TwoWayChannelMessage.Builder ack = Protos.TwoWayChannelMessage.newBuilder();
      ack.setType(Protos.TwoWayChannelMessage.MessageType.PAYMENT_ACK);
      if (ackInfoFuture == null) {
        conn.sendToClient(ack.build());
      } else {
        Futures.addCallback(
            ackInfoFuture,
            new FutureCallback<ByteString>() {
              @Override
              public void onSuccess(@Nullable ByteString result) {
                if (result != null) ack.setPaymentAck(ack.getPaymentAckBuilder().setInfo(result));
                conn.sendToClient(ack.build());
              }

              @Override
              public void onFailure(Throwable t) {
                log.info("Failed retrieving paymentIncrease info future");
                error(
                    "Failed processing payment update",
                    Protos.Error.ErrorCode.OTHER,
                    CloseReason.UPDATE_PAYMENT_FAILED);
              }
            });
      }
    }

    if (!stillUsable) {
      log.info("Channel is now fully exhausted, closing/initiating settlement");
      settlePayment(CloseReason.CHANNEL_EXHAUSTED);
    }
  }
  /**
   * 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();
    }
  }
 /**
  * Called to indicate the connection has been opened and messages can now be generated for the
  * client.
  */
 public void connectionOpen() {
   lock.lock();
   try {
     log.info("New server channel active.");
     connectionOpen = true;
   } finally {
     lock.unlock();
   }
 }
 @GuardedBy("lock")
 private void receiveCloseMessage() throws InsufficientMoneyException {
   log.info("Got CLOSE message, closing channel");
   if (state != null) {
     settlePayment(CloseReason.CLIENT_REQUESTED_CLOSE);
   } else {
     conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
   }
 }
 private void multisigContractPropogated(
     Protos.ProvideContract providedContract, Sha256Hash contractHash) {
   lock.lock();
   try {
     if (!connectionOpen || channelSettling) return;
     state.storeChannelInWallet(PaymentChannelServer.this);
     try {
       receiveUpdatePaymentMessage(providedContract.getInitialPayment(), false /* no ack msg */);
     } catch (VerificationException e) {
       log.error("Initial payment failed to verify", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
       return;
     } catch (ValueOutOfRangeException e) {
       log.error("Initial payment value was out of range", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
       return;
     } catch (InsufficientMoneyException e) {
       // This shouldn't happen because the server shouldn't allow itself to get into this
       // situation in the
       // first place, by specifying a min up front payment.
       log.error(
           "Tried to settle channel and could not afford the fees whilst updating payment", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
       return;
     }
     conn.sendToClient(
         Protos.TwoWayChannelMessage.newBuilder()
             .setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
             .build());
     step = InitStep.CHANNEL_OPEN;
     conn.channelOpen(contractHash);
   } finally {
     lock.unlock();
   }
 }
 private void error(String message, Protos.Error.ErrorCode errorCode, CloseReason closeReason) {
   log.error(message);
   Protos.Error.Builder errorBuilder;
   errorBuilder = Protos.Error.newBuilder().setCode(errorCode).setExplanation(message);
   conn.sendToClient(
       Protos.TwoWayChannelMessage.newBuilder()
           .setError(errorBuilder)
           .setType(Protos.TwoWayChannelMessage.MessageType.ERROR)
           .build());
   conn.destroyConnection(closeReason);
 }
  @GuardedBy("lock")
  private void receiveContractMessage(Protos.TwoWayChannelMessage msg)
      throws VerificationException {
    checkState(step == InitStep.WAITING_ON_CONTRACT && msg.hasProvideContract());
    log.info("Got contract, broadcasting and responding with CHANNEL_OPEN");
    final Protos.ProvideContract providedContract = msg.getProvideContract();

    // TODO notify connection handler that timeout should be significantly extended as we wait for
    // network propagation?
    final Transaction multisigContract =
        new Transaction(wallet.getParams(), providedContract.getTx().toByteArray());
    step = InitStep.WAITING_ON_MULTISIG_ACCEPTANCE;
    state
        .provideMultiSigContract(multisigContract)
        .addListener(
            new Runnable() {
              @Override
              public void run() {
                multisigContractPropogated(providedContract, multisigContract.getHash());
              }
            },
            Threading.SAME_THREAD);
  }
  @GuardedBy("lock")
  private void receiveRefundMessage(Protos.TwoWayChannelMessage msg) throws VerificationException {
    checkState(step == InitStep.WAITING_ON_UNSIGNED_REFUND && msg.hasProvideRefund());
    log.info("Got refund transaction, returning signature");

    Protos.ProvideRefund providedRefund = msg.getProvideRefund();
    state = new PaymentChannelServerState(broadcaster, wallet, myKey, expireTime);
    byte[] signature =
        state.provideRefundTransaction(
            new Transaction(wallet.getParams(), providedRefund.getTx().toByteArray()),
            providedRefund.getMultisigKey().toByteArray());

    step = InitStep.WAITING_ON_CONTRACT;

    Protos.ReturnRefund.Builder returnRefundBuilder =
        Protos.ReturnRefund.newBuilder().setSignature(ByteString.copyFrom(signature));

    conn.sendToClient(
        Protos.TwoWayChannelMessage.newBuilder()
            .setReturnRefund(returnRefundBuilder)
            .setType(Protos.TwoWayChannelMessage.MessageType.RETURN_REFUND)
            .build());
  }
 /**
  * Called when a message is received from the client. Processes the given message and generates
  * events based on its content.
  */
 public void receiveMessage(Protos.TwoWayChannelMessage msg) {
   lock.lock();
   try {
     checkState(connectionOpen);
     if (channelSettling) return;
     // If we generate an error, we set errorBuilder and closeReason and break, otherwise we return
     Protos.Error.Builder errorBuilder;
     CloseReason closeReason;
     try {
       switch (msg.getType()) {
         case CLIENT_VERSION:
           receiveVersionMessage(msg);
           return;
         case PROVIDE_REFUND:
           receiveRefundMessage(msg);
           return;
         case PROVIDE_CONTRACT:
           receiveContractMessage(msg);
           return;
         case UPDATE_PAYMENT:
           checkState(step == InitStep.CHANNEL_OPEN && msg.hasUpdatePayment());
           receiveUpdatePaymentMessage(msg.getUpdatePayment(), true);
           return;
         case CLOSE:
           receiveCloseMessage();
           return;
         case ERROR:
           checkState(msg.hasError());
           log.error(
               "Client sent ERROR {} with explanation {}",
               msg.getError().getCode().name(),
               msg.getError().hasExplanation() ? msg.getError().getExplanation() : "");
           conn.destroyConnection(CloseReason.REMOTE_SENT_ERROR);
           return;
         default:
           final String errorText =
               "Got unknown message type or type that doesn't apply to servers.";
           error(
               errorText,
               Protos.Error.ErrorCode.SYNTAX_ERROR,
               CloseReason.REMOTE_SENT_INVALID_MESSAGE);
       }
     } catch (VerificationException e) {
       log.error("Caught verification exception handling message from client", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
     } catch (ValueOutOfRangeException e) {
       log.error("Caught value out of range exception handling message from client", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
     } catch (InsufficientMoneyException e) {
       log.error("Caught insufficient money exception handling message from client", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.BAD_TRANSACTION,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
     } catch (IllegalStateException e) {
       log.error("Caught illegal state exception handling message from client", e);
       error(
           e.getMessage(),
           Protos.Error.ErrorCode.SYNTAX_ERROR,
           CloseReason.REMOTE_SENT_INVALID_MESSAGE);
     }
   } 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());
  }