diff --git a/core/src/main/java/io/bitsquare/dao/tokens/MockTxService.java b/core/src/main/java/io/bitsquare/dao/tokens/MockTxService.java index aa6628f71e..f1cd615e9d 100644 --- a/core/src/main/java/io/bitsquare/dao/tokens/MockTxService.java +++ b/core/src/main/java/io/bitsquare/dao/tokens/MockTxService.java @@ -21,15 +21,20 @@ import java.util.HashMap; import java.util.Map; class MockTxService extends TxService { - private final Tx genesisTx; private Map txMap = new HashMap<>(); - public MockTxService(Tx genesisTx) { - txMap.put(genesisTx.id, genesisTx); - this.genesisTx = genesisTx; + public MockTxService() { } public Tx getTx(String txId) { return txMap.get(txId); } + + public void addTx(Tx tx) { + txMap.put(tx.id, tx); + } + + public void cleanup() { + txMap = new HashMap<>(); + } } diff --git a/core/src/main/java/io/bitsquare/dao/tokens/TransactionParser.java b/core/src/main/java/io/bitsquare/dao/tokens/TransactionParser.java index 967dbc40f2..11cdd300ba 100644 --- a/core/src/main/java/io/bitsquare/dao/tokens/TransactionParser.java +++ b/core/src/main/java/io/bitsquare/dao/tokens/TransactionParser.java @@ -27,7 +27,7 @@ import java.util.List; public class TransactionParser { private static final Logger log = LoggerFactory.getLogger(TransactionParser.class); - private String genesisTxId = "83a4454747e5c972f2eb20d587538a330dd30b5cf468f8faea32eae640cebe79"; + private String genesisTxId; private TxService txService; /////////////////////////////////////////////////////////////////////////////////////////// @@ -44,45 +44,52 @@ public class TransactionParser { return txService.getTx(txId); } - - public void start() { - Tx genesisTx = getTx(genesisTxId); - List allUTXOs = findAllUTXOs(genesisTx); + public void applyIsTokenForAllOutputs(Tx parentTx) { + if (parentTx.id.equals(genesisTxId)) { + // direct output from genesisTx + parentTx.outputs.stream().forEach(e -> e.isToken = true); + } else { + // we are not a direct output so we check if our inputs are valid and sufficiently funded with tokens + int accumulatedTokenInputValue = 0; + for (TxInput input : parentTx.inputs) { + if (isValidInput(input)) { + accumulatedTokenInputValue += input.value; + } + } + log.debug("accumulatedTokenInputValue " + accumulatedTokenInputValue); + List outputs = parentTx.outputs; + for (int i = 0; i < outputs.size(); i++) { + TxOutput out = outputs.get(i); + log.debug("index {}, out.value {}, available input value {}", i, out.value, accumulatedTokenInputValue); + accumulatedTokenInputValue -= out.value; + // If we had enough token funds for our output we are a valid token output + out.isToken = accumulatedTokenInputValue >= 0; + } + } } - public List findAllUTXOs(Tx tx) { + public List getAllUTXOs(Tx tx) { List allUTXOs = new ArrayList<>(); - getOutputs(tx).stream().forEach(txOutput -> { - if (txOutput.isSpent) { - allUTXOs.addAll(findAllUTXOs(txOutput.spentByTxInput.parentTx)); - } else if (isValidOutput(txOutput)) { - allUTXOs.add(txOutput); - } else { - log.warn("invalid output " + txOutput); - } - }); + tx.outputs.stream() + .filter(e -> e.isToken) + .forEach(output -> { + if (!output.isSpent) { + allUTXOs.add(output); + } else { + allUTXOs.addAll(getAllUTXOs(output.inputOfSpendingTx.tx)); + } + }); return allUTXOs; } public boolean isValidOutput(TxOutput output) { - output.parentTx.inputs.forEach(input -> { - if (isValidInput(input)) { - - } - }); - return true; + return !output.isSpent && output.isToken; } + public boolean isValidInput(TxInput input) { - input.parentTx.outputs.forEach(output -> { - - }); - return true; - } - - private List getOutputs(Tx tx) { - return new ArrayList<>(); + return input.isToken || input.tx.id.equals(genesisTxId) || (input.output != null && input.output.isToken); } diff --git a/core/src/main/java/io/bitsquare/dao/tokens/Tx.java b/core/src/main/java/io/bitsquare/dao/tokens/Tx.java index 8a918fc98b..6b5f31b61c 100644 --- a/core/src/main/java/io/bitsquare/dao/tokens/Tx.java +++ b/core/src/main/java/io/bitsquare/dao/tokens/Tx.java @@ -36,14 +36,21 @@ public class Tx { public List outputs = new ArrayList<>(); public void addOutput(TxOutput output) { - output.parentTx = this; + output.tx = this; output.index = outputs.size(); outputs.add(output); } public void addInput(TxInput input) { - input.parentTx = this; + input.tx = this; input.index = inputs.size(); + + // TODO our mocks have null values, might be not null in production + if (input.output != null) { + input.output.isSpent = true; + input.output.inputOfSpendingTx = input; + input.value = input.output.value; + } inputs.add(input); } @@ -67,4 +74,13 @@ public class Tx { result = 31 * result + (inputs != null ? inputs.hashCode() : 0); return result; } + + @Override + public String toString() { + return "Tx{" + + "id='" + id + '\'' + + ", inputs=" + inputs + + ", outputs=" + outputs + + '}'; + } } diff --git a/core/src/main/java/io/bitsquare/dao/tokens/TxInput.java b/core/src/main/java/io/bitsquare/dao/tokens/TxInput.java index 06efc8c9d4..aa93d993ac 100644 --- a/core/src/main/java/io/bitsquare/dao/tokens/TxInput.java +++ b/core/src/main/java/io/bitsquare/dao/tokens/TxInput.java @@ -17,16 +17,31 @@ package io.bitsquare.dao.tokens; -public class TxInput { - public TxInput(Tx parentTx, TxOutput output, long value) { - this.parentTx = parentTx; - this.output = output; - this.value = value; - } +import java.util.UUID; +public class TxInput { + public final String id; public TxOutput output; - public Tx parentTx; + public Tx tx; public long value; public int index; - public boolean isValid; + public boolean isToken; + + public TxInput(Tx parentTx, TxOutput output) { + this.tx = parentTx; + this.output = output; + id = UUID.randomUUID().toString(); + } + + + @Override + public String toString() { + return "TxInput{" + + "output.id=" + (output != null ? output.id : "null") + + ", tx.id=" + (tx != null ? tx.id : "null") + + ", value=" + value + + ", index=" + index + + ", isToken=" + isToken + + '}'; + } } diff --git a/core/src/main/java/io/bitsquare/dao/tokens/TxOutput.java b/core/src/main/java/io/bitsquare/dao/tokens/TxOutput.java index b091b35827..c800459ca6 100644 --- a/core/src/main/java/io/bitsquare/dao/tokens/TxOutput.java +++ b/core/src/main/java/io/bitsquare/dao/tokens/TxOutput.java @@ -17,18 +17,34 @@ package io.bitsquare.dao.tokens; -public class TxOutput { - public TxOutput(String address, long value) { - this.address = address; - this.value = value; - } +import java.util.UUID; - public Tx parentTx; +public class TxOutput { + public final String id; + public Tx tx; + public TxInput inputOfSpendingTx; public boolean isSpent; - public TxInput spentByTxInput; public String address; public long value; public int index; - public boolean isValid; + public boolean isToken; + public TxOutput(String address, long value) { + this.address = address; + this.value = value; + id = UUID.randomUUID().toString(); + } + + @Override + public String toString() { + return "TxOutput{" + + "tx.id=" + (tx != null ? tx.id : "null") + + ", inputOfSpendingTx.id=" + (inputOfSpendingTx != null ? inputOfSpendingTx.id : "null") + + ", isSpent=" + isSpent + + ", address='" + address + '\'' + + ", value=" + value + + ", index=" + index + + ", isToken=" + isToken + + '}'; + } } diff --git a/core/src/test/java/io/bitsquare/dao/tokens/TransactionParserTest.java b/core/src/test/java/io/bitsquare/dao/tokens/TransactionParserTest.java index 2fdc97fb07..f2656e3ad5 100644 --- a/core/src/test/java/io/bitsquare/dao/tokens/TransactionParserTest.java +++ b/core/src/test/java/io/bitsquare/dao/tokens/TransactionParserTest.java @@ -17,48 +17,128 @@ package io.bitsquare.dao.tokens; -import org.bitcoinj.core.Coin; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.List; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertEquals; public class TransactionParserTest { - private Tx genesisTx; + private static final Logger log = LoggerFactory.getLogger(TransactionParserTest.class); + private MockTxService txService; private TransactionParser transactionParser; + + private Tx genesisTx; + private Tx tx1; private TxOutput output1; private TxOutput output2; private TxInput input1; + private TxOutput output1_1; + private TxInput input2; + private TxOutput output2_1; + private TxInput genesisInput; @Before public void setup() { - genesisTx = getGenesisTx(); - txService = new MockTxService(genesisTx); - transactionParser = new TransactionParser("123", txService); + txService = new MockTxService(); + transactionParser = new TransactionParser("id_genesis", txService); } @After public void tearDown() { + txService.cleanup(); } @Test public void testGetTx() { - Tx genesisTxResult = transactionParser.getTx("id_genesis"); - assertEquals(genesisTx, genesisTxResult); + assertEquals(createGenesisTx(), transactionParser.getTx("id_genesis")); + assertEquals(createTx1(), transactionParser.getTx("id_tx1")); } @Test - public void testTx1() { - Tx genesisTxResult = transactionParser.getTx("id_genesis"); - assertEquals(genesisTx, genesisTxResult); + public void testValidTxs() { + transactionParser.applyIsTokenForAllOutputs(createGenesisTx()); + assertTrue(transactionParser.isValidInput(genesisInput)); + assertTrue(transactionParser.isValidOutput(output1)); + assertTrue(transactionParser.isValidOutput(output1)); + + transactionParser.applyIsTokenForAllOutputs(createTx1()); + assertTrue(transactionParser.isValidInput(input1)); + assertFalse(transactionParser.isValidOutput(output1)); + assertTrue(transactionParser.isValidOutput(output1_1)); + assertTrue(transactionParser.isValidOutput(output2)); + + transactionParser.applyIsTokenForAllOutputs(createTx2()); + assertTrue(transactionParser.isValidInput(input1)); + assertTrue(transactionParser.isValidInput(input2)); + assertFalse(transactionParser.isValidOutput(output1)); + assertTrue(transactionParser.isValidOutput(output1_1)); + assertFalse(transactionParser.isValidOutput(output2)); + assertTrue(transactionParser.isValidOutput(output2_1)); + } - private Tx getGenesisTx() { + @Test + public void testGetAllUTXOs() { + Tx genesisTx = createGenesisTx(); + transactionParser.applyIsTokenForAllOutputs(genesisTx); + List allUTXOs = transactionParser.getAllUTXOs(genesisTx); + assertEquals(2, allUTXOs.size()); + allUTXOs.stream().forEach(output -> { + log.debug("output " + output); + assertTrue(transactionParser.isValidOutput(output)); + }); + + transactionParser.applyIsTokenForAllOutputs(createTx1()); + allUTXOs = transactionParser.getAllUTXOs(genesisTx); + assertEquals(2, allUTXOs.size()); + allUTXOs.stream().forEach(output -> { + log.debug("output " + output); + assertTrue(transactionParser.isValidOutput(output)); + }); + + transactionParser.applyIsTokenForAllOutputs(createTx2()); + allUTXOs = transactionParser.getAllUTXOs(genesisTx); + assertEquals(2, allUTXOs.size()); + allUTXOs.stream().forEach(output -> { + log.debug("output " + output); + assertTrue(transactionParser.isValidOutput(output)); + }); + } + + + @Test + public void testInvalidTxs() { + transactionParser.applyIsTokenForAllOutputs(createGenesisTx()); + + transactionParser.applyIsTokenForAllOutputs(createInvalidTx1_tooHighValue()); + assertTrue(transactionParser.isValidInput(input1)); + assertFalse(transactionParser.isValidOutput(output1)); // spent + + assertFalse(transactionParser.isValidOutput(output1_1)); // to high value + assertTrue(transactionParser.isValidOutput(output2)); + + + transactionParser.applyIsTokenForAllOutputs(createTx1()); + assertTrue(transactionParser.isValidInput(input1)); + assertFalse(transactionParser.isValidOutput(output1)); + assertTrue(transactionParser.isValidOutput(output1_1)); + assertTrue(transactionParser.isValidOutput(output2)); + } + + private Tx createGenesisTx() { Tx tx = new Tx("id_genesis"); - tx.addInput(new TxInput(new Tx("id_0001", null, null), null, Coin.COIN.value)); + genesisInput = new TxInput(new Tx("id_genesisInput", null, null), null); + genesisInput.value = 10_000; + tx.addInput(genesisInput); output1 = new TxOutput("addr_1", 1000); tx.addOutput(output1); @@ -66,20 +146,55 @@ public class TransactionParserTest { output2 = new TxOutput("addr_2", 2000); tx.addOutput(output2); + txService.addTx(tx); return tx; } - private Tx getTx1() { + private Tx createTx1() { Tx tx = new Tx("id_tx1"); - input1 = new TxInput(genesisTx, output1, Coin.COIN.value); + input1 = new TxInput(genesisTx, output1); tx.addInput(input1); - - TxInput feeInput = new TxInput(new Tx("id_0001", null, null), null, 20_000); - Tx feeTx = new Tx("id_fee_1", null, null); - tx.addOutput(new TxOutput("addr_1", 1000)); - tx.addOutput(new TxOutput("addr_2", 2000)); + TxInput feeInput = new TxInput(new Tx("id_fee_1", null, null), null); + tx.addInput(feeInput); + + output1_1 = new TxOutput("addr_1_1", 1000); + tx.addOutput(output1_1); + + txService.addTx(tx); + return tx; + } + + private Tx createTx2() { + Tx tx = new Tx("id_tx2"); + + input2 = new TxInput(genesisTx, output2); + tx.addInput(input2); + + TxInput feeInput = new TxInput(new Tx("id_fee_2", null, null), null); + tx.addInput(feeInput); + + output2_1 = new TxOutput("addr_2_1", 1000); + tx.addOutput(output2_1); + + txService.addTx(tx); + return tx; + } + + private Tx createInvalidTx1_tooHighValue() { + Tx tx = new Tx("id_tx1"); + + input1 = new TxInput(genesisTx, output1); + tx.addInput(input1); + + TxInput feeInput = new TxInput(new Tx("id_fee_1", null, null), null); + tx.addInput(feeInput); + + output1_1 = new TxOutput("addr_1_1", 4000); + tx.addOutput(output1_1); + + txService.addTx(tx); return tx; } }