/** * 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; }
/** * For a connected transaction, runs the script against the connected pubkey and verifies they are * correct. * * @throws ScriptException if the script did not verify. * @throws VerificationException If the outpoint doesn't match the given output. */ public void verify() throws VerificationException { final Transaction fromTx = getOutpoint().fromTx; long spendingIndex = getOutpoint().getIndex(); checkNotNull(fromTx, "Not connected"); final TransactionOutput output = fromTx.getOutput((int) spendingIndex); verify(output); }
/** * When the servers signature for the refund transaction is received, call this to verify it and * sign the complete refund ourselves. * * <p>If this does not throw an exception, we are secure against the loss of funds and can safely * provide the server with the multi-sig contract to lock in the agreement. In this case, both the * multisig contract and the refund transaction are automatically committed to wallet so that it * can handle broadcasting the refund transaction at the appropriate time if necessary. */ public synchronized void provideRefundSignature(byte[] theirSignature) throws VerificationException { checkNotNull(theirSignature); checkState(state == State.WAITING_FOR_SIGNED_REFUND); TransactionSignature theirSig = TransactionSignature.decodeFromBitcoin(theirSignature, true); if (theirSig.sigHashMode() != Transaction.SigHash.NONE || !theirSig.anyoneCanPay()) throw new VerificationException("Refund signature was not SIGHASH_NONE|SIGHASH_ANYONECANPAY"); // Sign the refund transaction ourselves. final TransactionOutput multisigContractOutput = multisigContract.getOutput(0); try { multisigScript = multisigContractOutput.getScriptPubKey(); } catch (ScriptException e) { throw new RuntimeException(e); // Cannot happen: we built this ourselves. } TransactionSignature ourSignature = refundTx.calculateSignature(0, myKey, multisigScript, Transaction.SigHash.ALL, false); // Insert the signatures. Script scriptSig = ScriptBuilder.createMultiSigInputScript(ourSignature, theirSig); log.info("Refund scriptSig: {}", scriptSig); log.info("Multi-sig contract scriptPubKey: {}", multisigScript); TransactionInput refundInput = refundTx.getInput(0); refundInput.setScriptSig(scriptSig); refundInput.verify(multisigContractOutput); state = State.SAVE_STATE_IN_WALLET; }
@Test public void testCreateMultiSigInputScript() throws AddressFormatException { // Setup transaction and signatures ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") .getKey(); ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo") .getKey(); ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg") .getKey(); Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); byte[] bytes = Hex.decode( "01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); Transaction transaction = new Transaction(params, bytes); TransactionOutput output = transaction.getOutput(1); Transaction spendTx = new Transaction(params); Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H"); Script outputScript = ScriptBuilder.createOutputScript(address); spendTx.addOutput(output.getValue(), outputScript); spendTx.addInput(output); Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false); ECKey.ECDSASignature party1Signature = key1.sign(sighash); ECKey.ECDSASignature party2Signature = key2.sign(sighash); TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false); TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false); // Create p2sh multisig input script Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript.getProgram()); // Assert that the input script contains 4 chunks assertTrue(inputScript.getChunks().size() == 4); // Assert that the input script created contains the original multisig // script as the last chunk ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram()); // Create regular multisig input script inputScript = ScriptBuilder.createMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature)); // Assert that the input script only contains 3 chunks assertTrue(inputScript.getChunks().size() == 3); // Assert that the input script created does not end with the original // multisig script scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertThat(scriptChunk.data, IsNot.not(IsEqual.equalTo(multisigScript.getProgram()))); }
// Create a payment transaction with valueToMe going back to us private synchronized Wallet.SendRequest makeUnsignedChannelContract(Coin valueToMe) { Transaction tx = new Transaction(wallet.getParams()); if (!totalValue.subtract(valueToMe).equals(Coin.ZERO)) { clientOutput.setValue(totalValue.subtract(valueToMe)); tx.addOutput(clientOutput); } tx.addInput(multisigContract.getOutput(0)); return Wallet.SendRequest.forTx(tx); }
/** Returns true if the tx is a valid settlement transaction. */ public synchronized boolean isSettlementTransaction(Transaction tx) { try { tx.verify(); tx.getInput(0).verify(multisigContract.getOutput(0)); return true; } catch (VerificationException e) { return false; } }
PaymentChannelClientState(StoredClientChannel storedClientChannel, Wallet wallet) throws VerificationException { // The PaymentChannelClientConnection handles storedClientChannel.active and ensures we aren't // resuming channels this.wallet = checkNotNull(wallet); this.multisigContract = checkNotNull(storedClientChannel.contract); this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); this.refundTx = checkNotNull(storedClientChannel.refund); this.refundFees = checkNotNull(storedClientChannel.refundFees); this.expiryTime = refundTx.getLockTime(); this.myKey = checkNotNull(storedClientChannel.myKey); this.serverMultisigKey = null; this.totalValue = multisigContract.getOutput(0).getValue(); this.valueToMe = checkNotNull(storedClientChannel.valueToMe); this.storedChannel = storedClientChannel; this.state = State.READY; initWalletListeners(); }
private synchronized Transaction makeUnsignedChannelContract(Coin valueToMe) throws ValueOutOfRangeException { Transaction tx = new Transaction(wallet.getParams()); tx.addInput(multisigContract.getOutput(0)); // Our output always comes first. // TODO: We should drop myKey in favor of output key + multisig key separation // (as its always obvious who the client is based on T2 output order) tx.addOutput(valueToMe, myKey.toAddress(wallet.getParams())); return tx; }
PaymentChannelServerState( StoredServerChannel storedServerChannel, Wallet wallet, TransactionBroadcaster broadcaster) throws VerificationException { synchronized (storedServerChannel) { this.wallet = checkNotNull(wallet); this.broadcaster = checkNotNull(broadcaster); this.multisigContract = checkNotNull(storedServerChannel.contract); this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); this.clientKey = ECKey.fromPublicOnly(multisigScript.getChunks().get(1).data); this.clientOutput = checkNotNull(storedServerChannel.clientOutput); this.refundTransactionUnlockTimeSecs = storedServerChannel.refundTransactionUnlockTimeSecs; this.serverKey = checkNotNull(storedServerChannel.myKey); this.totalValue = multisigContract.getOutput(0).getValue(); this.bestValueToMe = checkNotNull(storedServerChannel.bestValueToMe); this.bestValueSignature = storedServerChannel.bestValueSignature; checkArgument(bestValueToMe.equals(Coin.ZERO) || bestValueSignature != null); this.storedServerChannel = storedServerChannel; storedServerChannel.state = this; this.state = State.READY; } }
@Test public void exceptionListener() throws Exception { wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { throw new NullPointerException("boo!"); } }); final Throwable[] throwables = new Throwable[1]; Threading.uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { throwables[0] = throwable; } }; // In real usage we're not really meant to adjust the uncaught exception handler after stuff // started happening // but in the unit test environment other tests have just run so the thread is probably still // kicking around. // Force it to crash so it'll be recreated with our new handler. Threading.USER_THREAD.execute( new Runnable() { @Override public void run() { throw new RuntimeException(); } }); connect(); Transaction t1 = new Transaction(unitTestParams); t1.addInput(new TransactionInput(unitTestParams, t1, new byte[] {})); t1.addOutput(Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams)); Transaction t2 = new Transaction(unitTestParams); t2.addInput(t1.getOutput(0)); t2.addOutput(Utils.toNanoCoins(1, 0), wallet.getChangeAddress()); inbound(writeTarget, t2); final InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Transaction, t2.getInput(0).getOutpoint().getHash()); final NotFoundMessage nfm = new NotFoundMessage(unitTestParams, Lists.newArrayList(inventoryItem)); inbound(writeTarget, nfm); pingAndWait(writeTarget); Threading.waitForUserCode(); assertTrue(throwables[0] instanceof NullPointerException); Threading.uncaughtExceptionHandler = null; }
/** * Called when the client provides the refund transaction. The refund transaction must have one * input from the multisig contract (that we don't have yet) and one output that the client * creates to themselves. This object will later be modified when we start getting paid. * * @param refundTx The refund transaction, this object will be mutated when payment is * incremented. * @param clientMultiSigPubKey The client's pubkey which is required for the multisig output * @return Our signature that makes the refund transaction valid * @throws VerificationException If the transaction isnt valid or did not meet the requirements of * a refund transaction. */ public synchronized byte[] provideRefundTransaction( Transaction refundTx, byte[] clientMultiSigPubKey) throws VerificationException { checkNotNull(refundTx); checkNotNull(clientMultiSigPubKey); checkState(state == State.WAITING_FOR_REFUND_TRANSACTION); log.info("Provided with refund transaction: {}", refundTx); // Do a few very basic syntax sanity checks. refundTx.verify(); // Verify that the refund transaction has a single input (that we can fill to sign the multisig // output). if (refundTx.getInputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one input"); // Verify that the refund transaction has a time lock on it and a sequence number of zero. if (refundTx.getInput(0).getSequenceNumber() != 0) throw new VerificationException("Refund transaction's input's sequence number is non-0"); if (refundTx.getLockTime() < minExpireTime) throw new VerificationException("Refund transaction has a lock time too soon"); // Verify the transaction has one output (we don't care about its contents, its up to the // client) // Note that because we sign with SIGHASH_NONE|SIGHASH_ANYOENCANPAY the client can later add // more outputs and // inputs, but we will need only one output later to create the paying transactions if (refundTx.getOutputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one output"); refundTransactionUnlockTimeSecs = refundTx.getLockTime(); // Sign the refund tx with the scriptPubKey and return the signature. We don't have the spending // transaction // so do the steps individually. clientKey = ECKey.fromPublicOnly(clientMultiSigPubKey); Script multisigPubKey = ScriptBuilder.createMultiSigOutputScript(2, ImmutableList.of(clientKey, serverKey)); // We are really only signing the fact that the transaction has a proper lock time and don't // care about anything // else, so we sign SIGHASH_NONE and SIGHASH_ANYONECANPAY. TransactionSignature sig = refundTx.calculateSignature(0, serverKey, multisigPubKey, Transaction.SigHash.NONE, true); log.info("Signed refund transaction."); this.clientOutput = refundTx.getOutput(0); state = State.WAITING_FOR_MULTISIG_CONTRACT; return sig.encodeToBitcoin(); }
/** * Connects this input to the relevant output of the referenced transaction. Connecting means * updating the internal pointers and spent flags. If the mode is to ABORT_ON_CONFLICT then the * spent output won't be changed, but the outpoint.fromTx pointer will still be updated. * * @param transaction The transaction to try. * @param mode Whether to abort if there's a pre-existing connection or not. * @return NO_SUCH_TX if transaction is not the prevtx, ALREADY_SPENT if there was a conflict, * SUCCESS if not. */ public ConnectionResult connect(Transaction transaction, ConnectMode mode) { if (!transaction.getHash().equals(outpoint.getHash())) return ConnectionResult.NO_SUCH_TX; checkElementIndex( (int) outpoint.getIndex(), transaction.getOutputs().size(), "Corrupt transaction"); TransactionOutput out = transaction.getOutput((int) outpoint.getIndex()); if (!out.isAvailableForSpending()) { if (getParentTransaction().equals(outpoint.fromTx)) { // Already connected. return ConnectionResult.SUCCESS; } else if (mode == ConnectMode.DISCONNECT_ON_CONFLICT) { out.markAsUnspent(); } else if (mode == ConnectMode.ABORT_ON_CONFLICT) { outpoint.fromTx = out.getParentTransaction(); return TransactionInput.ConnectionResult.ALREADY_SPENT; } } connect(out); return TransactionInput.ConnectionResult.SUCCESS; }
/** * This is required for signatures which use a sigHashType which cannot be represented using * SigHash and anyoneCanPay See transaction * c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73, which has sigHashType 0 */ public static synchronized byte[] serializeForSignature( Transaction spendingTx, int inputIndex, byte[] connectedScript, byte sigHashType) { NetworkParameters params = TestNet3Params.get(); // The SIGHASH flags are used in the design of contracts, please see this page for a further // understanding of // the purposes of the code in this method: // // https://en.bitcoin.it/wiki/Contracts try { Transaction tx = new Transaction(params, spendingTx.bitcoinSerialize()); // Store all the input scripts and clear them in preparation for signing. If we're signing a // fresh // transaction that step isn't very helpful, but it doesn't add much cost relative to the // actual // EC math so we'll do it anyway. // // Also store the input sequence numbers in case we are clearing them with SigHash.NONE/SINGLE byte[][] inputScripts = new byte[tx.getInputs().size()][]; long[] inputSequenceNumbers = new long[tx.getInputs().size()]; for (int i = 0; i < tx.getInputs().size(); i++) { inputScripts[i] = tx.getInputs().get(i).getScriptBytes(); inputSequenceNumbers[i] = tx.getInputs().get(i).getSequenceNumber(); tx.getInput(i).setScriptSig(new Script(new byte[0])); } // This step has no purpose beyond being synchronized with the reference clients bugs. // OP_CODESEPARATOR // is a legacy holdover from a previous, broken design of executing scripts that shipped in // Bitcoin 0.1. // It was seriously flawed and would have let anyone take anyone elses money. Later versions // switched to // the design we use today where scripts are executed independently but share a stack. This // left the // OP_CODESEPARATOR instruction having no purpose as it was only meant to be used internally, // not actually // ever put into scripts. Deleting OP_CODESEPARATOR is a step that should never be required // but if we don't // do it, we could split off the main chain. connectedScript = Script.removeAllInstancesOfOp(connectedScript, ScriptOpCodes.OP_CODESEPARATOR); // Set the input to the script of its output. Satoshi does this but the step has no obvious // purpose as // the signature covers the hash of the prevout transaction which obviously includes the // output script // already. Perhaps it felt safer to him in some way, or is another leftover from how the code // was written. TransactionInput input = tx.getInputs().get(inputIndex); input.setScriptSig(new Script(connectedScript)); List<TransactionOutput> outputs = tx.getOutputs(); if ((sigHashType & 0x1f) == (Transaction.SigHash.NONE.ordinal() + 1)) { // SIGHASH_NONE means no outputs are signed at all - the signature is effectively for a // "blank cheque". // this.outputs = new ArrayList<TransactionOutput>(0); tx.clearOutputs(); // The signature isn't broken by new versions of the transaction issued by other parties. for (int i = 0; i < tx.getInputs().size(); i++) if (i != inputIndex) tx.getInputs().get(i).setSequenceNumber(0); } else if ((sigHashType & 0x1f) == (Transaction.SigHash.SINGLE.ordinal() + 1)) { // SIGHASH_SINGLE means only sign the output at the same index as the input (ie, my output). if (inputIndex >= tx.getOutputs().size()) { // The input index is beyond the number of outputs, it's a buggy signature made by a // broken // Bitcoin implementation. The reference client also contains a bug in handling this case: // any transaction output that is signed in this case will result in both the signed // output // and any future outputs to this public key being steal-able by anyone who has // the resulting signature and the public key (both of which are part of the signed tx // input). // Put the transaction back to how we found it. // // TODO: Only allow this to happen if we are checking a signature, not signing a // transactions for (int i = 0; i < tx.getInputs().size(); i++) { // tx.getInputs().get(i).setScriptSig(inputScripts[i]); /* tx.getInputs().get(i).setScriptSig(ScriptBuilder.createMultiSigInputScriptBytes( Arrays.asList(inputScripts[i])));*/ tx.getInput(i).setScriptSig(new Script(inputScripts[i])); tx.getInputs().get(i).setSequenceNumber(inputSequenceNumbers[i]); } // this.outputs = outputs; // Satoshis bug is that SignatureHash was supposed to return a hash and on this codepath // it // actually returns the constant "1" to indicate an error, which is never checked for. // Oops. return Utils.HEX.decode( "0100000000000000000000000000000000000000000000000000000000000000"); } // In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs // before // that position are "nulled out". Unintuitively, the value in a "null" transaction is set // to -1. /* this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex + 1)); for (int i = 0; i < inputIndex; i++) this.outputs.set(i, new TransactionOutput(params, this, Coin.NEGATIVE_SATOSHI, new byte[] {})); // The signature isn't broken by new versions of the transaction issued by other parties. for (int i = 0; i < inputs.size(); i++) if (i != inputIndex) inputs.get(i).setSequenceNumber(0);*/ // In SIGHASH_SINGLE the outputs after the matching input index are deleted, and the outputs // before // that position are "nulled out". Unintuitively, the value in a "null" transaction is set // to -1. // tx.outputs = new ArrayList<TransactionOutput>(tx.getOutputs().subList(0, inputIndex + // 1)); tx.clearOutputs(); for (int i = 0; i <= inputIndex; i++) if (i == inputIndex) { // need to make sure the output at inputIndex stays the same tx.addOutput(spendingTx.getOutput(inputIndex)); } else { // this.outputs.set(i, new TransactionOutput(params, this, Coin.NEGATIVE_SATOSHI, new // byte[] {})); tx.addOutput(new TransactionOutput(params, tx, Coin.NEGATIVE_SATOSHI, new byte[] {})); } // The signature isn't broken by new versions of the transaction issued by other parties. for (int i = 0; i < tx.getInputs().size(); i++) if (i != inputIndex) tx.getInputs().get(i).setSequenceNumber(0); } List<TransactionInput> inputs = tx.getInputs(); if ((sigHashType & (byte) 0x80) == 0x80) { // SIGHASH_ANYONECANPAY means the signature in the input is not broken by // changes/additions/removals // of other inputs. For example, this is useful for building assurance contracts. tx.clearInputs(); tx.getInputs().add(input); } ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(256); tx.bitcoinSerialize(bos); // We also have to write a hash type (sigHashType is actually an unsigned char) uint32ToByteStreamLE(0x000000ff & sigHashType, bos); // Note that this is NOT reversed to ensure it will be signed correctly. If it were to be // printed out // however then we would expect that it is IS reversed. byte[] txSignatureBytes = bos.toByteArray(); bos.close(); // Put the transaction back to how we found it. // tx.inputs = inputs; tx.clearInputs(); for (int i = 0; i < inputs.size(); i++) { tx.addInput(inputs.get(i)); } for (int i = 0; i < inputs.size(); i++) { inputs.get(i).setScriptSig(new Script(inputScripts[i])); inputs.get(i).setSequenceNumber(inputSequenceNumbers[i]); } // this.outputs = outputs; tx.clearOutputs(); for (int i = 0; i < outputs.size(); i++) { tx.addOutput(outputs.get(i)); } return txSignatureBytes; } catch (IOException e) { throw new RuntimeException(e); // Cannot happen. } }
private void checkTimeLockedDependency(boolean shouldAccept, boolean useNotFound) throws Exception { // Initial setup. connectWithVersion(useNotFound ? 70001 : 60001); ECKey key = new ECKey(); Wallet wallet = new Wallet(unitTestParams); wallet.addKey(key); wallet.setAcceptRiskyTransactions(shouldAccept); peer.addWallet(wallet); final Transaction[] vtx = new Transaction[1]; wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { vtx[0] = tx; } }); // t1 -> t2 [locked] -> t3 (not available) Transaction t2 = new Transaction(unitTestParams); t2.setLockTime(999999); // Add a fake input to t3 that goes nowhere. Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8"))); t2.addInput( new TransactionInput( unitTestParams, t2, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, t3))); t2.getInput(0).setSequenceNumber(0xDEADBEEF); t2.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addOutput(Utils.toNanoCoins(1, 0), key); // Make it relevant. // Announce t1. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); // Send it. GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); // Nothing arrived at our event listener yet. assertNull(vtx[0]); // We request t2. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t2); if (!useNotFound) bouncePing(); // We request t3. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t3, getdata.getItems().get(0).hash); // Can't find it: bottom of tree. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); Threading.waitForUserCode(); // We're done but still not notified because it was timelocked. if (shouldAccept) assertNotNull(vtx[0]); else assertNull(vtx[0]); }
public void recursiveDownload(boolean useNotFound) throws Exception { // Using ping or notfound? connectWithVersion(useNotFound ? 70001 : 60001); // Check that we can download all dependencies of an unconfirmed relevant transaction from the // mempool. ECKey to = new ECKey(); final Transaction[] onTx = new Transaction[1]; peer.addEventListener( new AbstractPeerEventListener() { @Override public void onTransaction(Peer peer1, Transaction t) { onTx[0] = t; } }, Threading.SAME_THREAD); // Make the some fake transactions in the following graph: // t1 -> t2 -> [t5] // -> t3 -> t4 -> [t6] // -> [t7] // -> [t8] // The ones in brackets are assumed to be in the chain and are represented only by hashes. Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to); Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash(); Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey()); Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash(); t4.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t3 = new Transaction(unitTestParams); t3.addInput(t4.getOutput(0)); t3.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addInput(t3.getOutput(0)); Sha256Hash someHash = new Sha256Hash("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, someHash))); Sha256Hash anotherHash = new Sha256Hash("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 1, anotherHash))); t1.addOutput(Utils.toNanoCoins(1, 0), to); t1 = TestUtils.roundTripTransaction(unitTestParams, t1); t2 = TestUtils.roundTripTransaction(unitTestParams, t2); t3 = TestUtils.roundTripTransaction(unitTestParams, t3); t4 = TestUtils.roundTripTransaction(unitTestParams, t4); // Announce the first one. Wait for it to be downloaded. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); Threading.waitForUserCode(); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); pingAndWait(writeTarget); assertEquals(t1, onTx[0]); // We want its dependencies so ask for them. ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1); assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(4, getdata.getItems().size()); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); assertEquals(t3.getHash(), getdata.getItems().get(1).hash); assertEquals(someHash, getdata.getItems().get(2).hash); assertEquals(anotherHash, getdata.getItems().get(3).hash); long nonce = -1; if (!useNotFound) nonce = ((Ping) outbound(writeTarget)).getNonce(); // For some random reason, t4 is delivered at this point before it's needed - perhaps it was a // Bloom filter // false positive. We do this to check that the mempool is being checked for seen transactions // before // requesting them. inbound(writeTarget, t4); // Deliver the requested transactions. inbound(writeTarget, t2); inbound(writeTarget, t3); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash)); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash)); inbound(writeTarget, notFound); } else { inbound(writeTarget, new Pong(nonce)); } assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already // found t4. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash()); // t5 isn't found and t4 is. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5)); inbound(writeTarget, notFound); } else { bouncePing(); } assertFalse(futures.isDone()); // Continue to explore the t4 branch and ask for t6, which is in the chain. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t6, getdata.getItems().get(0).hash); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); // That's it, we explored the entire tree. assertTrue(futures.isDone()); List<Transaction> results = futures.get(); assertTrue(results.contains(t2)); assertTrue(results.contains(t3)); assertTrue(results.contains(t4)); }
private static void send( List<String> outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException { try { // Convert the input strings to outputs. Transaction t = new Transaction(params); for (String spec : outputs) { String[] parts = spec.split(":"); if (parts.length != 2) { System.err.println("Malformed output specification, must have two parts separated by :"); return; } String destination = parts[0]; try { BigInteger value = Utils.toNanoCoins(parts[1]); if (destination.startsWith("0")) { boolean compressed = destination.startsWith("02") || destination.startsWith("03"); // Treat as a raw public key. BigInteger pubKey = new BigInteger(destination, 16); ECKey key = new ECKey(null, pubKey.toByteArray(), compressed); t.addOutput(value, key); } else { // Treat as an address. Address addr = new Address(params, destination); t.addOutput(value, addr); } } catch (WrongNetworkException e) { System.err.println( "Malformed output specification, address is for a different network: " + parts[0]); return; } catch (AddressFormatException e) { System.err.println( "Malformed output specification, could not parse as address: " + parts[0]); return; } catch (NumberFormatException e) { System.err.println( "Malformed output specification, could not parse as value: " + parts[1]); } } Wallet.SendRequest req = Wallet.SendRequest.forTx(t); if (t.getOutputs().size() == 1 && t.getOutput(0).getValue().equals(wallet.getBalance())) { log.info("Emptying out wallet, recipient may get less than what you expect"); req.emptyWallet = true; } req.fee = fee; if (allowUnconfirmed) { wallet.allowSpendingUnconfirmedTransactions(); } if (password != null) { if (!wallet.checkPassword(password)) { System.err.println("Password is incorrect."); return; } req.aesKey = wallet.getKeyCrypter().deriveKey(password); } wallet.completeTx(req); try { if (lockTimeStr != null) { t.setLockTime(Transaction.parseLockTimeStr(lockTimeStr)); // For lock times to take effect, at least one output must have a non-final sequence // number. t.getInputs().get(0).setSequenceNumber(0); // And because we modified the transaction after it was completed, we must re-sign the // inputs. t.signInputs(Transaction.SigHash.ALL, wallet); } } catch (ParseException e) { System.err.println("Could not understand --locktime of " + lockTimeStr); return; } catch (ScriptException e) { throw new RuntimeException(e); } t = req.tx; // Not strictly required today. System.out.println(t.getHashAsString()); if (options.has("offline")) { wallet.commitTx(t); return; } setup(); peers.startAndWait(); // Wait for peers to connect, the tx to be sent to one of them and for it to be propagated // across the // network. Once propagation is complete and we heard the transaction back from all our peers, // it will // be committed to the wallet. peers.broadcastTransaction(t).get(); // Hack for regtest/single peer mode, as we're about to shut down and won't get an ACK from // the remote end. List<Peer> peerList = peers.getConnectedPeers(); if (peerList.size() == 1) peerList.get(0).ping().get(); } catch (BlockStoreException e) { throw new RuntimeException(e); } catch (KeyCrypterException e) { throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } catch (InsufficientMoneyException e) { System.err.println( "Insufficient funds: have " + Utils.bitcoinValueToFriendlyString(wallet.getBalance())); } }