From 00c03e1c055c44b191885338565bf4537612ad2c Mon Sep 17 00:00:00 2001 From: Oscar Guindzberg Date: Thu, 25 Aug 2016 18:43:58 -0300 Subject: [PATCH] TransactionInput.disconnect() - make it work for UTXO based wallet --- .../org/bitcoinj/core/TransactionInput.java | 20 +++- .../bitcoinj/core/TransactionOutPoint.java | 2 +- .../bitcoinj/core/TransactionInputTest.java | 97 +++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/org/bitcoinj/core/TransactionInputTest.java diff --git a/core/src/main/java/org/bitcoinj/core/TransactionInput.java b/core/src/main/java/org/bitcoinj/core/TransactionInput.java index 4b38e3c8..790e8870 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionInput.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionInput.java @@ -359,11 +359,23 @@ public class TransactionInput extends ChildMessage { * @return true if the disconnection took place, false if it was not connected. */ public boolean disconnect() { - if (outpoint.fromTx == null) return false; - TransactionOutput output = outpoint.fromTx.getOutput((int) outpoint.getIndex()); - if (output.getSpentBy() == this) { - output.markAsUnspent(); + TransactionOutput connectedOutput; + if (outpoint.fromTx != null) { + // The outpoint is connected using a "standard" wallet, disconnect it. + connectedOutput = outpoint.fromTx.getOutput((int) outpoint.getIndex()); outpoint.fromTx = null; + } else if (outpoint.connectedOutput != null) { + // The outpoint is connected using a UTXO based wallet, disconnect it. + connectedOutput = outpoint.connectedOutput; + outpoint.connectedOutput = null; + } else { + // The outpoint is not connected, do nothing. + return false; + } + + if (connectedOutput != null && connectedOutput.getSpentBy() == this) { + // The outpoint was connected to an output, disconnect the output. + connectedOutput.markAsUnspent(); return true; } else { return false; diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java index 2ddfe5f1..1e0b9f95 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java @@ -44,7 +44,7 @@ public class TransactionOutPoint extends ChildMessage { Transaction fromTx; // The connected output. - private TransactionOutput connectedOutput; + TransactionOutput connectedOutput; public TransactionOutPoint(NetworkParameters params, long index, @Nullable Transaction fromTx) { super(params); diff --git a/core/src/test/java/org/bitcoinj/core/TransactionInputTest.java b/core/src/test/java/org/bitcoinj/core/TransactionInputTest.java new file mode 100644 index 00000000..73f98f76 --- /dev/null +++ b/core/src/test/java/org/bitcoinj/core/TransactionInputTest.java @@ -0,0 +1,97 @@ +/* + * Copyright by the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.List; + +import org.bitcoinj.params.UnitTestParams; +import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.testing.FakeTxBuilder; +import org.bitcoinj.wallet.AllowUnconfirmedCoinSelector; +import org.bitcoinj.wallet.SendRequest; +import org.bitcoinj.wallet.Wallet; +import org.junit.Test; + +import com.google.common.collect.Lists; + +public class TransactionInputTest { + + @Test + public void testStandardWalletDisconnect() throws Exception { + NetworkParameters params = UnitTestParams.get(); + Wallet w = new Wallet(new Context(params)); + w.setCoinSelector(new AllowUnconfirmedCoinSelector()); + Address a = w.currentReceiveAddress(); + Transaction tx1 = FakeTxBuilder.createFakeTxWithoutChangeAddress(params, Coin.COIN, a); + w.receivePending(tx1, null); + Transaction tx2 = new Transaction(params); + tx2.addOutput(Coin.valueOf(99000000), new ECKey()); + w.completeTx(SendRequest.forTx(tx2)); + + TransactionInput txInToDisconnect = tx2.getInput(0); + + assertEquals(tx1, txInToDisconnect.getOutpoint().fromTx); + assertNull(txInToDisconnect.getOutpoint().connectedOutput); + + txInToDisconnect.disconnect(); + + assertNull(txInToDisconnect.getOutpoint().fromTx); + assertNull(txInToDisconnect.getOutpoint().connectedOutput); + } + + @Test + public void testUTXOWalletDisconnect() throws Exception { + final NetworkParameters params = UnitTestParams.get(); + Wallet w = new Wallet(new Context(params)); + Address a = w.currentReceiveAddress(); + final UTXO utxo = new UTXO(Sha256Hash.of(new byte[] { 1, 2, 3 }), 1, Coin.COIN, 0, false, + ScriptBuilder.createOutputScript(a)); + w.setUTXOProvider(new UTXOProvider() { + @Override + public NetworkParameters getParams() { + return params; + } + + @Override + public List getOpenTransactionOutputs(List
addresses) throws UTXOProviderException { + return Lists.newArrayList(utxo); + } + + @Override + public int getChainHeadHeight() throws UTXOProviderException { + return Integer.MAX_VALUE; + } + }); + + Transaction tx2 = new Transaction(params); + tx2.addOutput(Coin.valueOf(99000000), new ECKey()); + w.completeTx(SendRequest.forTx(tx2)); + + TransactionInput txInToDisconnect = tx2.getInput(0); + + assertNull(txInToDisconnect.getOutpoint().fromTx); + assertEquals(utxo.getHash(), txInToDisconnect.getOutpoint().connectedOutput.getParentTransactionHash()); + + txInToDisconnect.disconnect(); + + assertNull(txInToDisconnect.getOutpoint().fromTx); + assertNull(txInToDisconnect.getOutpoint().connectedOutput); + } +}