コード例 #1
0
  /**
   * Called when the client provides the multi-sig contract. Checks that the previously-provided
   * refund transaction spends this transaction (because we will use it as a base to create payment
   * transactions) as well as output value and form (ie it is a 2-of-2 multisig to the correct
   * keys).
   *
   * @param multisigContract The provided multisig contract. Do not mutate this object after this
   *     call.
   * @return A future which completes when the provided multisig contract successfully broadcasts,
   *     or throws if the broadcast fails for some reason Note that if the network simply rejects
   *     the transaction, this future will never complete, a timeout should be used.
   * @throws VerificationException If the provided multisig contract is not well-formed or does not
   *     meet previously-specified parameters
   */
  public synchronized ListenableFuture<PaymentChannelServerState> provideMultiSigContract(
      final Transaction multisigContract) throws VerificationException {
    checkNotNull(multisigContract);
    checkState(state == State.WAITING_FOR_MULTISIG_CONTRACT);
    try {
      multisigContract.verify();
      this.multisigContract = multisigContract;
      this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();

      // Check that multisigContract's first output is a 2-of-2 multisig to the correct pubkeys in
      // the correct order
      final Script expectedScript =
          ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(clientKey, serverKey));
      if (!Arrays.equals(multisigScript.getProgram(), expectedScript.getProgram()))
        throw new VerificationException(
            "Multisig contract's first output was not a standard 2-of-2 multisig to client and server in that order.");

      this.totalValue = multisigContract.getOutput(0).getValue();
      if (this.totalValue.signum() <= 0)
        throw new VerificationException(
            "Not accepting an attempt to open a contract with zero value.");
    } catch (VerificationException e) {
      // We couldn't parse the multisig transaction or its output.
      log.error("Provided multisig contract did not verify: {}", multisigContract.toString());
      throw e;
    }
    log.info("Broadcasting multisig contract: {}", multisigContract);
    state = State.WAITING_FOR_MULTISIG_ACCEPTANCE;
    final SettableFuture<PaymentChannelServerState> future = SettableFuture.create();
    Futures.addCallback(
        broadcaster.broadcastTransaction(multisigContract).future(),
        new FutureCallback<Transaction>() {
          @Override
          public void onSuccess(Transaction transaction) {
            log.info(
                "Successfully broadcast multisig contract {}. Channel now open.",
                transaction.getHashAsString());
            try {
              // Manually add the multisigContract to the wallet, overriding the isRelevant checks
              // so we can track
              // it and check for double-spends later
              wallet.receivePending(multisigContract, null, true);
            } catch (VerificationException e) {
              throw new RuntimeException(
                  e); // Cannot happen, we already called multisigContract.verify()
            }
            state = State.READY;
            future.set(PaymentChannelServerState.this);
          }

          @Override
          public void onFailure(Throwable throwable) {
            // Couldn't broadcast the transaction for some reason.
            log.error("Broadcast multisig contract failed", throwable);
            state = State.ERROR;
            future.setException(throwable);
          }
        });
    return future;
  }
コード例 #2
0
  /**
   * Closes this channel and broadcasts the highest value payment transaction on the network.
   *
   * <p>This will set the state to {@link State#CLOSED} if the transaction is successfully broadcast
   * on the network. If we fail to broadcast for some reason, the state is set to {@link
   * State#ERROR}.
   *
   * <p>If the current state is before {@link State#READY} (ie we have not finished initializing the
   * channel), we simply set the state to {@link State#CLOSED} and let the client handle getting its
   * refund transaction confirmed.
   *
   * @return a future which completes when the provided multisig contract successfully broadcasts,
   *     or throws if the broadcast fails for some reason. Note that if the network simply rejects
   *     the transaction, this future will never complete, a timeout should be used.
   * @throws InsufficientMoneyException If the payment tx would have cost more in fees to spend than
   *     it is worth.
   */
  public synchronized ListenableFuture<Transaction> close() throws InsufficientMoneyException {
    if (storedServerChannel != null) {
      StoredServerChannel temp = storedServerChannel;
      storedServerChannel = null;
      StoredPaymentChannelServerStates channels =
          (StoredPaymentChannelServerStates)
              wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
      channels.closeChannel(
          temp); // May call this method again for us (if it wasn't the original caller)
      if (state.compareTo(State.CLOSING) >= 0) return closedFuture;
    }

    if (state.ordinal() < State.READY.ordinal()) {
      log.error("Attempt to settle channel in state " + state);
      state = State.CLOSED;
      closedFuture.set(null);
      return closedFuture;
    }
    if (state != State.READY) {
      // TODO: What is this codepath for?
      log.warn("Failed attempt to settle a channel in state " + state);
      return closedFuture;
    }
    Transaction tx = null;
    try {
      Wallet.SendRequest req = makeUnsignedChannelContract(bestValueToMe);
      tx = req.tx;
      // Provide a throwaway signature so that completeTx won't complain out about unsigned inputs
      // it doesn't
      // know how to sign. Note that this signature does actually have to be valid, so we can't use
      // a dummy
      // signature to save time, because otherwise completeTx will try to re-sign it to make it
      // valid and then
      // die. We could probably add features to the SendRequest API to make this a bit more
      // efficient.
      signMultisigInput(tx, Transaction.SigHash.NONE, true);
      // Let wallet handle adding additional inputs/fee as necessary.
      req.shuffleOutputs = false;
      req.missingSigsMode = Wallet.MissingSigsMode.USE_DUMMY_SIG;
      wallet.completeTx(req); // TODO: Fix things so shuffling is usable.
      feePaidForPayment = req.tx.getFee();
      log.info("Calculated fee is {}", feePaidForPayment);
      if (feePaidForPayment.compareTo(bestValueToMe) > 0) {
        final String msg =
            String.format(
                Locale.US,
                "Had to pay more in fees (%s) than the channel was worth (%s)",
                feePaidForPayment,
                bestValueToMe);
        throw new InsufficientMoneyException(feePaidForPayment.subtract(bestValueToMe), msg);
      }
      // Now really sign the multisig input.
      signMultisigInput(tx, Transaction.SigHash.ALL, false);
      // Some checks that shouldn't be necessary but it can't hurt to check.
      tx.verify(); // Sanity check syntax.
      for (TransactionInput input : tx.getInputs())
        input.verify(); // Run scripts and ensure it is valid.
    } catch (InsufficientMoneyException e) {
      throw e; // Don't fall through.
    } catch (Exception e) {
      log.error(
          "Could not verify self-built tx\nMULTISIG {}\nCLOSE {}",
          multisigContract,
          tx != null ? tx : "");
      throw new RuntimeException(e); // Should never happen.
    }
    state = State.CLOSING;
    log.info("Closing channel, broadcasting tx {}", tx);
    // The act of broadcasting the transaction will add it to the wallet.
    ListenableFuture<Transaction> future = broadcaster.broadcastTransaction(tx).future();
    Futures.addCallback(
        future,
        new FutureCallback<Transaction>() {
          @Override
          public void onSuccess(Transaction transaction) {
            log.info("TX {} propagated, channel successfully closed.", transaction.getHash());
            state = State.CLOSED;
            closedFuture.set(transaction);
          }

          @Override
          public void onFailure(Throwable throwable) {
            log.error("Failed to settle channel, could not broadcast", throwable);
            state = State.ERROR;
            closedFuture.setException(throwable);
          }
        });
    return closedFuture;
  }