mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-20 18:33:43 +01:00
Add tests, input/output checks and getUTXOs
This commit is contained in:
parent
a961c3088c
commit
3a0491b4b7
@ -21,15 +21,20 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
class MockTxService extends TxService {
|
||||
private final Tx genesisTx;
|
||||
private Map<String, Tx> 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<>();
|
||||
}
|
||||
}
|
||||
|
@ -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<TxOutput> 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<TxOutput> 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<TxOutput> findAllUTXOs(Tx tx) {
|
||||
public List<TxOutput> getAllUTXOs(Tx tx) {
|
||||
List<TxOutput> 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<TxOutput> getOutputs(Tx tx) {
|
||||
return new ArrayList<>();
|
||||
return input.isToken || input.tx.id.equals(genesisTxId) || (input.output != null && input.output.isToken);
|
||||
}
|
||||
|
||||
|
||||
|
@ -36,14 +36,21 @@ public class Tx {
|
||||
public List<TxOutput> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -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<TxOutput> 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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user