diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 9d58a681..9fe4dfbc 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -1,12 +1,24 @@ package org.bitcoinj.core; +import org.bitcoinj.core.TransactionConfidence.ConfidenceType; import org.bitcoinj.params.UnitTestParams; +import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.testing.FakeTxBuilder; import org.junit.Before; import org.junit.Test; +import org.easymock.EasyMock; import static org.junit.Assert.assertEquals; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.replay; + +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; /** * Just check the Transaction.verify() method. Most methods that have complicated logic in Transaction are tested @@ -24,9 +36,24 @@ public class TransactionTest { @Before public void setUp() throws Exception { dummy = FakeTxBuilder.createFakeTx(PARAMS, Coin.COIN, ADDRESS); - tx = new Transaction(PARAMS); - tx.addOutput(Coin.COIN, ADDRESS); - tx.addInput(dummy.getOutput(0)); + tx = newTransaction(); + } + + private Transaction newTransaction(boolean newToAddress) { + Address addr = newToAddress ? new ECKey().toAddress(PARAMS): ADDRESS; + return newTransaction(new TransactionOutput(PARAMS, null, Coin.COIN, addr)); + } + + private Transaction newTransaction() { + return newTransaction(new TransactionOutput(PARAMS, null, Coin.COIN, ADDRESS)); + } + + private Transaction newTransaction(TransactionOutput to) { + Transaction newTx = new Transaction(PARAMS); + newTx.addOutput(to); + newTx.addInput(dummy.getOutput(0)); + + return newTx; } @Test(expected = VerificationException.EmptyInputsOrOutputs.class) @@ -89,4 +116,184 @@ public class TransactionTest { assertEquals(101, input.getScriptBytes().length); tx.verify(); } -} + + @Test + public void isConsistentReturnsFalseAsExpected() { + TransactionBag mockTB = createMock(TransactionBag.class); + + TransactionOutput to = createMock(TransactionOutput.class); + EasyMock.expect(to.isAvailableForSpending()).andReturn(true); + EasyMock.expect(to.isMineOrWatched(mockTB)).andReturn(true); + EasyMock.expect(to.getSpentBy()).andReturn(new TransactionInput(PARAMS, null, new byte[0])); + + Transaction tx = newTransaction(to); + + replay(to); + + boolean isConsistent = tx.isConsistent(mockTB, false); + + assertEquals(isConsistent, false); + } + + @Test + public void isConsistentReturnsFalseAsExpected_WhenAvailableForSpendingEqualsFalse() { + TransactionOutput to = createMock(TransactionOutput.class); + EasyMock.expect(to.isAvailableForSpending()).andReturn(false); + EasyMock.expect(to.getSpentBy()).andReturn(null); + + Transaction tx = newTransaction(to); + + replay(to); + + boolean isConsistent = tx.isConsistent(createMock(TransactionBag.class), false); + + assertEquals(isConsistent, false); + } + + @Test + public void testEstimatedLockTime_WhenParameterSignifiesBlockHeight() { + int TEST_LOCK_TIME = 20; + Date now = Calendar.getInstance().getTime(); + + BlockChain mockBlockChain = createMock(BlockChain.class); + EasyMock.expect(mockBlockChain.estimateBlockTime(TEST_LOCK_TIME)).andReturn(now); + + Transaction tx = newTransaction(); + tx.setLockTime(TEST_LOCK_TIME); // less than five hundred million + + replay(mockBlockChain); + + assertEquals(tx.estimateLockTime(mockBlockChain), now); + } + + @Test + public void testOptimalEncodingMessageSize() { + Transaction tx = new Transaction(PARAMS); + + int length = tx.length; + + // add basic transaction input, check the length + tx.addOutput(new TransactionOutput(PARAMS, null, Coin.COIN, ADDRESS)); + length += getCombinedLength(tx.getOutputs()); + + // add basic output, check the length + tx.addInput(dummy.getOutput(0)); + length += getCombinedLength(tx.getInputs()); + + // optimal encoding size should equal the length we just calculated + assertEquals(tx.getOptimalEncodingMessageSize(), length); + } + + private int getCombinedLength(List list) { + int sumOfAllMsgSizes = 0; + for (Message m: list) { sumOfAllMsgSizes += m.getMessageSize() + 1; } + return sumOfAllMsgSizes; + } + + @Test + public void testIsMatureReturnsFalseIfTransactionIsCoinbaseAndConfidenceTypeIsNotEqualToBuilding() { + Transaction tx = new Transaction(PARAMS); + tx.addInput(dummy.getOutput(0)); + + // make this into a coinbase transaction + TransactionInput input = tx.getInput(0); + input.getOutpoint().setHash(Sha256Hash.ZERO_HASH); + input.getOutpoint().setIndex(-1); + + tx.getConfidence().setConfidenceType(ConfidenceType.UNKNOWN); + assertEquals(tx.isMature(), false); + + tx.getConfidence().setConfidenceType(ConfidenceType.PENDING); + assertEquals(tx.isMature(), false); + + tx.getConfidence().setConfidenceType(ConfidenceType.DEAD); + assertEquals(tx.isMature(), false); + } + + @Test + public void testToStringWhenLockTimeIsSpecifiedInBlockHeight() { + Transaction tx = newTransaction(); + TransactionInput input = tx.getInput(0); + input.setSequenceNumber(42); + + int TEST_LOCK_TIME = 20; + tx.setLockTime(TEST_LOCK_TIME); + + Calendar cal = Calendar.getInstance(); + cal.set(2085, 10, 4, 17, 53, 21); + cal.set(Calendar.MILLISECOND, 0); + + BlockChain mockBlockChain = createMock(BlockChain.class); + EasyMock.expect(mockBlockChain.estimateBlockTime(TEST_LOCK_TIME)).andReturn(cal.getTime()); + + replay(mockBlockChain); + + String str = tx.toString(mockBlockChain); + + assertEquals(str.contains("block " + TEST_LOCK_TIME), true); + assertEquals(str.contains("estimated to be reached at"), true); + } + + @Test + public void testToStringWhenIteratingOverAnInputCatchesAnException() { + Transaction tx = newTransaction(); + TransactionInput ti = new TransactionInput(PARAMS, tx, new byte[0]) { + @Override + public Script getScriptSig() throws ScriptException { + throw new ScriptException(""); + } + }; + + tx.addInput(ti); + assertEquals(tx.toString().contains("[exception: "), true); + } + + @Test + public void testToStringWhenThereAreZeroInputs() { + Transaction tx = new Transaction(PARAMS); + assertEquals(tx.toString().contains("No inputs!"), true); + } + + @Test + public void testTheTXByHeightComparator() { + final boolean USE_UNIQUE_ADDRESS = true; + Transaction tx1 = newTransaction(USE_UNIQUE_ADDRESS); + tx1.getConfidence().setAppearedAtChainHeight(1); + + Transaction tx2 = newTransaction(USE_UNIQUE_ADDRESS); + tx2.getConfidence().setAppearedAtChainHeight(2); + + Transaction tx3 = newTransaction(USE_UNIQUE_ADDRESS); + tx3.getConfidence().setAppearedAtChainHeight(3); + + SortedSet set = new TreeSet(Transaction.SORT_TX_BY_HEIGHT); + set.add(tx2); + set.add(tx1); + set.add(tx3); + + Iterator iterator = set.iterator(); + + assertEquals(tx1.equals(tx2), false); + assertEquals(tx1.equals(tx3), false); + assertEquals(tx1.equals(tx1), true); + + assertEquals(iterator.next().equals(tx3), true); + assertEquals(iterator.next().equals(tx2), true); + assertEquals(iterator.next().equals(tx1), true); + assertEquals(iterator.hasNext(), false); + } + + @Test(expected = ScriptException.class) + public void testAddSignedInputThrowsExceptionWhenScriptIsNotToRawPubKeyAndIsNotToAddress() { + ECKey key = new ECKey(); + Address addr = key.toAddress(PARAMS); + Transaction fakeTx = FakeTxBuilder.createFakeTx(PARAMS, Coin.COIN, addr); + + Transaction tx = new Transaction(PARAMS); + tx.addOutput(fakeTx.getOutput(0)); + + Script script = ScriptBuilder.createOpReturnScript(new byte[0]); + + tx.addSignedInput(fakeTx.getOutput(0).getOutPointFor(), script, key); + } +} \ No newline at end of file