/** * 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; }
@Override public String toString() { final String newline = String.format(Locale.US, "%n"); final String closeStr = close == null ? "still open" : close.toString().replaceAll(newline, newline + " "); return String.format( Locale.US, "Stored client channel for server ID %s (%s)%n" + " Key: %s%n" + " Value left: %s%n" + " Refund fees: %s%n" + " Contract: %s" + "Refund: %s" + "Close: %s", id, active ? "active" : "inactive", myKey, valueToMe, refundFees, contract.toString().replaceAll(newline, newline + " "), refund.toString().replaceAll(newline, newline + " "), closeStr); }
@Override public synchronized String toString() { final String newline = String.format(Locale.US, "%n"); return String.format( Locale.US, "Stored server channel (%s)%n" + " Version: %d%n" + " Key: %s%n" + " Value to me: %s%n" + " Client output: %s%n" + " Refund unlock: %s (%d unix time)%n" + " Contract: %s%n", connectedHandler != null ? "connected" : "disconnected", majorVersion, myKey, bestValueToMe, clientOutput, new Date(refundTransactionUnlockTimeSecs * 1000), refundTransactionUnlockTimeSecs, contract.toString().replaceAll(newline, newline + " ")); }