/** * 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; }
/** * Converts the given wallet to the object representation of the protocol buffers. This can be * modified, or additional data fields set, before serialization takes place. */ public Protos.Wallet walletToProto(Wallet wallet) { Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder(); walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId()); if (wallet.getDescription() != null) { walletBuilder.setDescription(wallet.getDescription()); } for (WalletTransaction wtx : wallet.getWalletTransactions()) { Protos.Transaction txProto = makeTxProto(wtx); walletBuilder.addTransaction(txProto); } walletBuilder.addAllKey(wallet.serializeKeychainToProtobuf()); for (Script script : wallet.getWatchedScripts()) { Protos.Script protoScript = Protos.Script.newBuilder() .setProgram(ByteString.copyFrom(script.getProgram())) .setCreationTimestamp(script.getCreationTimeSeconds() * 1000) .build(); walletBuilder.addWatchedScript(protoScript); } // Populate the lastSeenBlockHash field. Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash(); if (lastSeenBlockHash != null) { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } if (wallet.getLastBlockSeenTimeSecs() > 0) walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs()); // Populate the scrypt parameters. KeyCrypter keyCrypter = wallet.getKeyCrypter(); if (keyCrypter == null) { // The wallet is unencrypted. walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED); } else { // The wallet is encrypted. walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType()); if (keyCrypter instanceof KeyCrypterScrypt) { KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter; walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters()); } else { // Some other form of encryption has been specified that we do not know how to persist. throw new RuntimeException( "The wallet has encryption of type '" + keyCrypter.getUnderstoodEncryptionType() + "' but this WalletProtobufSerializer does not know how to persist this."); } } if (wallet.getKeyRotationTime() != null) { long timeSecs = wallet.getKeyRotationTime().getTime() / 1000; walletBuilder.setKeyRotationTime(timeSecs); } populateExtensions(wallet, walletBuilder); for (Map.Entry<String, ByteString> entry : wallet.getTags().entrySet()) { Protos.Tag.Builder tag = Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue()); walletBuilder.addTags(tag); } for (TransactionSigner signer : wallet.getTransactionSigners()) { // do not serialize LocalTransactionSigner as it's being added implicitly if (signer instanceof LocalTransactionSigner) continue; Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder(); protoSigner.setClassName(signer.getClass().getName()); protoSigner.setData(ByteString.copyFrom(signer.serialize())); walletBuilder.addTransactionSigners(protoSigner); } walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend()); // Populate the wallet version. walletBuilder.setVersion(wallet.getVersion()); return walletBuilder.build(); }
/** * Set the given program as the scriptSig that is supposed to satisfy the connected output script. */ public void setScriptSig(Script scriptSig) { this.scriptSig = new WeakReference<Script>(checkNotNull(scriptSig)); // TODO: This should all be cleaned up so we have a consistent internal representation. setScriptBytes(scriptSig.getProgram()); }