Search in sources :

Example 1 with TempTx

use of bisq.core.dao.state.blockchain.TempTx in project bisq-core by bisq-network.

the class TxParser method findGenesisTx.

/**
 * Parse and return the genesis transaction for bisq, if applicable.
 *
 * @param genesisTxId         The transaction id of the bisq genesis transaction.
 * @param genesisBlockHeight  The block height of the bisq genesis transaction.
 * @param genesisTotalSupply  The total supply of the genesis issuance for bisq.
 * @param rawTx               The candidate transaction.
 * @return The genesis transaction if applicable, or Optional.empty() otherwise.
 */
public static Optional<TempTx> findGenesisTx(String genesisTxId, int genesisBlockHeight, Coin genesisTotalSupply, RawTx rawTx) {
    boolean isGenesis = rawTx.getBlockHeight() == genesisBlockHeight && rawTx.getId().equals(genesisTxId);
    if (!isGenesis)
        return Optional.empty();
    TempTx tempTx = TempTx.fromRawTx(rawTx);
    tempTx.setTxType(TxType.GENESIS);
    long remainingInputValue = genesisTotalSupply.getValue();
    for (int i = 0; i < tempTx.getTempTxOutputs().size(); ++i) {
        TempTxOutput txOutput = tempTx.getTempTxOutputs().get(i);
        long value = txOutput.getValue();
        boolean isValid = value <= remainingInputValue;
        if (!isValid)
            throw new InvalidGenesisTxException("Genesis tx is invalid; using more than available inputs. " + "Remaining input value is " + remainingInputValue + " sat; tx info: " + tempTx.toString());
        remainingInputValue -= value;
        txOutput.setTxOutputType(TxOutputType.GENESIS_OUTPUT);
    }
    if (remainingInputValue > 0) {
        throw new InvalidGenesisTxException("Genesis tx is invalid; not using all available inputs. " + "Remaining input value is " + remainingInputValue + " sat, tx info: " + tempTx.toString());
    }
    return Optional.of(tempTx);
}
Also used : TempTx(bisq.core.dao.state.blockchain.TempTx) InvalidGenesisTxException(bisq.core.dao.node.parser.exceptions.InvalidGenesisTxException) TempTxOutput(bisq.core.dao.state.blockchain.TempTxOutput)

Example 2 with TempTx

use of bisq.core.dao.state.blockchain.TempTx in project bisq-core by bisq-network.

the class TxParserTest method testGetBisqTxType.

@Test
public void testGetBisqTxType() {
    // Thu Jun 20 14:04:25 CEST 2013
    long time = 1371729865;
    final List<TxInput> inputs = Arrays.asList(new TxInput("tx0", 0, null), new TxInput("tx1", 1, null));
    RawTxOutput output = new RawTxOutput(0, 123, null, null, null, null, 100);
    RawTx rawTx = new RawTx("faketx0", 100, "fakeblock0", time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
    TempTx tempTx = TempTx.fromRawTx(rawTx);
    boolean hasOpReturnCandidate = true;
    long remainingInputValue = 0;
    Optional<OpReturnType> optionalOpReturnType = Optional.empty();
    TxType result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    TxType want = TxType.INVALID;
    Assert.assertEquals("With an OP_RETURN candidate but no optional OP_RETURN type, this tx should be invalid.", want, result);
    hasOpReturnCandidate = false;
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.TRANSFER_BSQ;
    Assert.assertEquals("With no OP_RETURN candidate and no optional OP_RETURN type, this should be a BSQ transfer tx.", want, result);
    // todo(chirhonul): this is very likely incorrect, we should see the tx as INVALID if
    // !hasOpReturnCandidate but optionalOpReturnType.
    hasOpReturnCandidate = false;
    optionalOpReturnType = Optional.of(OpReturnType.LOCKUP);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.LOCKUP;
    Assert.assertEquals("With no OP_RETURN candidate and optional OP_RETURN type of LOCKUP, this should be a LOCKUP tx.", want, result);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.BLIND_VOTE);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.BLIND_VOTE;
    Assert.assertEquals("With OP_RETURN candidate and optional OP_RETURN type of BLIND_VOTE, this should be a BLIND_VOTE tx.", want, result);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.VOTE_REVEAL);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.VOTE_REVEAL;
    Assert.assertEquals("With OP_RETURN candidate and optional OP_RETURN type of VOTE_REVEAL, this should be a VOTE_REVEAL tx.", want, result);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.PROPOSAL);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.PROPOSAL;
    Assert.assertEquals("With OP_RETURN candidate and optional OP_RETURN type of PROPOSAL, this should be a PROPOSAL tx.", want, result);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.COMPENSATION_REQUEST);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.INVALID;
    Assert.assertEquals("COMPENSATION_REQUEST has fewer than three outputs, this should be a INVALID tx.", want, result);
    RawTxOutput output1 = new RawTxOutput(0, 123, null, null, null, null, 100);
    RawTxOutput output2 = new RawTxOutput(0, 456, null, null, null, null, 100);
    RawTxOutput output3 = new RawTxOutput(0, 678, null, null, null, null, 100);
    rawTx = new RawTx("faketx1", 200, "fakeblock1", time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output1, output2, output3)));
    tempTx = TempTx.fromRawTx(rawTx);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.COMPENSATION_REQUEST);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.INVALID;
    Assert.assertEquals("Output 1 at COMPENSATION_REQUEST has to be a ISSUANCE_CANDIDATE_OUTPUT, this should be a INVALID tx.", want, result);
    hasOpReturnCandidate = true;
    optionalOpReturnType = Optional.of(OpReturnType.COMPENSATION_REQUEST);
    tempTx.getTempTxOutputs().get(1).setTxOutputType(TxOutputType.ISSUANCE_CANDIDATE_OUTPUT);
    result = TxParser.getBisqTxType(tempTx, hasOpReturnCandidate, remainingInputValue, optionalOpReturnType);
    want = TxType.COMPENSATION_REQUEST;
    Assert.assertEquals("With OP_RETURN candidate and optional OP_RETURN type of COMPENSATION_REQUEST, this should be a COMPENSATION_REQUEST tx.", want, result);
}
Also used : TempTx(bisq.core.dao.state.blockchain.TempTx) RawTxOutput(bisq.core.dao.state.blockchain.RawTxOutput) TxType(bisq.core.dao.state.blockchain.TxType) RawTx(bisq.core.dao.state.blockchain.RawTx) OpReturnType(bisq.core.dao.state.blockchain.OpReturnType) TxInput(bisq.core.dao.state.blockchain.TxInput) Test(org.junit.Test)

Example 3 with TempTx

use of bisq.core.dao.state.blockchain.TempTx in project bisq-core by bisq-network.

the class TxParserTest method testGetGenesisTx.

@Test
public void testGetGenesisTx() {
    int blockHeight = 200;
    String blockHash = "abc123";
    Coin genesisTotalSupply = Coin.parseCoin("2.5");
    // Thu Jun 20 14:04:25 CEST 2013
    long time = 1371729865;
    final List<TxInput> inputs = Arrays.asList(new TxInput("tx0", 0, null), new TxInput("tx1", 1, null));
    RawTxOutput output = new RawTxOutput(0, genesisTotalSupply.value, null, null, null, null, blockHeight);
    RawTx rawTx = new RawTx("tx2", blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
    String genesisTxId = "genesisTxId";
    int genesisBlockHeight = 150;
    // With mismatch in block height and tx id, we should not get genesis tx back.
    Optional<TempTx> result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
    Optional<TempTx> want = Optional.empty();
    Assert.assertEquals(want, result);
    // With correct block height but mismatch in tx id, we should still not get genesis tx back.
    blockHeight = 150;
    rawTx = new RawTx("tx2", blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
    result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
    want = Optional.empty();
    Assert.assertEquals(want, result);
    // With correct tx id and block height, we should find our genesis tx with correct tx and output type.
    rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
    result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
    TempTx tempTx = TempTx.fromRawTx(rawTx);
    tempTx.setTxType(TxType.GENESIS);
    for (int i = 0; i < tempTx.getTempTxOutputs().size(); ++i) {
        tempTx.getTempTxOutputs().get(i).setTxOutputType(TxOutputType.GENESIS_OUTPUT);
    }
    want = Optional.of(tempTx);
    Assert.assertEquals(want, result);
    // With correct tx id and block height, but too low sum of outputs (lower than genesisTotalSupply), we
    // should see an exception raised.
    output = new RawTxOutput(0, genesisTotalSupply.value - 1, null, null, null, null, blockHeight);
    rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output)));
    try {
        result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
        Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too low");
    } catch (InvalidGenesisTxException igtxe) {
        String wantMessage = "Genesis tx is invalid; not using all available inputs. Remaining input value is 1 sat";
        Assert.assertTrue("Unexpected exception, want message starting with " + "'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
    }
    // With correct tx id and block height, but too high sum of outputs (higher than from genesisTotalSupply), we
    // should see an exception raised.
    RawTxOutput output1 = new RawTxOutput(0, genesisTotalSupply.value - 2, null, null, null, null, blockHeight);
    RawTxOutput output2 = new RawTxOutput(0, 3, null, null, null, null, blockHeight);
    rawTx = new RawTx(genesisTxId, blockHeight, blockHash, time, ImmutableList.copyOf(inputs), ImmutableList.copyOf(Arrays.asList(output1, output2)));
    try {
        result = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
        Assert.fail("Expected an InvalidGenesisTxException to be thrown when outputs are too high");
    } catch (InvalidGenesisTxException igtxe) {
        String wantMessage = "Genesis tx is invalid; using more than available inputs. Remaining input value is 2 sat";
        Assert.assertTrue("Unexpected exception, want message starting with " + "'" + wantMessage + "', got '" + igtxe.getMessage() + "'", igtxe.getMessage().startsWith(wantMessage));
    }
}
Also used : Coin(org.bitcoinj.core.Coin) TempTx(bisq.core.dao.state.blockchain.TempTx) RawTxOutput(bisq.core.dao.state.blockchain.RawTxOutput) InvalidGenesisTxException(bisq.core.dao.node.parser.exceptions.InvalidGenesisTxException) RawTx(bisq.core.dao.state.blockchain.RawTx) TxInput(bisq.core.dao.state.blockchain.TxInput) Test(org.junit.Test)

Example 4 with TempTx

use of bisq.core.dao.state.blockchain.TempTx in project bisq-core by bisq-network.

the class TxParser method findTx.

// Apply state changes to tx, inputs and outputs
// return true if any input contained BSQ
// Any tx with BSQ input is a BSQ tx (except genesis tx but that is not handled in
// that class).
// There might be txs without any valid BSQ txOutput but we still keep track of it,
// for instance to calculate the total burned BSQ.
public Optional<Tx> findTx(RawTx rawTx, String genesisTxId, int genesisBlockHeight, Coin genesisTotalSupply) {
    txInputParser = new TxInputParser(bsqStateService);
    txOutputParser = new TxOutputParser(bsqStateService);
    // Let's see if we have a genesis tx
    Optional<TempTx> optionalGenesisTx = TxParser.findGenesisTx(genesisTxId, genesisBlockHeight, genesisTotalSupply, rawTx);
    if (optionalGenesisTx.isPresent()) {
        TempTx genesisTx = optionalGenesisTx.get();
        txOutputParser.processGenesisTxOutput(genesisTx);
        return Optional.of(Tx.fromTempTx(genesisTx));
    }
    // If it is not a genesis tx we continue to parse to see if it is a valid BSQ tx.
    int blockHeight = rawTx.getBlockHeight();
    // We could pass tx also to the sub validators but as long we have not refactored the validators to pure
    // functions lets use the parsingModel.
    TempTx tempTx = TempTx.fromRawTx(rawTx);
    for (int inputIndex = 0; inputIndex < tempTx.getTxInputs().size(); inputIndex++) {
        TxInput input = tempTx.getTxInputs().get(inputIndex);
        TxOutputKey outputKey = input.getConnectedTxOutputKey();
        txInputParser.process(outputKey, blockHeight, rawTx.getId(), inputIndex);
    }
    long accumulatedInputValue = txInputParser.getAccumulatedInputValue();
    txOutputParser.setAvailableInputValue(accumulatedInputValue);
    txOutputParser.setUnlockBlockHeight(txInputParser.getUnlockBlockHeight());
    txOutputParser.setOptionalSpentLockupTxOutput(txInputParser.getOptionalSpentLockupTxOutput());
    // TODO remove
    txOutputParser.setTempTx(tempTx);
    boolean hasBsqInputs = accumulatedInputValue > 0;
    if (hasBsqInputs) {
        final List<TempTxOutput> outputs = tempTx.getTempTxOutputs();
        // We start with last output as that might be an OP_RETURN output and gives us the specific tx type, so it is
        // easier and cleaner at parsing the other outputs to detect which kind of tx we deal with.
        // Setting the opReturn type here does not mean it will be a valid BSQ tx as the checks are only partial and
        // BSQ inputs are not verified yet.
        // We keep the temporary opReturn type in the parsingModel object.
        checkArgument(!outputs.isEmpty(), "outputs must not be empty");
        int lastIndex = outputs.size() - 1;
        txOutputParser.processOpReturnCandidate(outputs.get(lastIndex));
        // We iterate all outputs including the opReturn to do a full validation including the BSQ fee
        for (int index = 0; index < outputs.size(); index++) {
            boolean isLastOutput = index == lastIndex;
            txOutputParser.processTxOutput(isLastOutput, outputs.get(index), index);
        }
        remainingInputValue = txOutputParser.getAvailableInputValue();
        processOpReturnType(blockHeight, tempTx);
        // We don't allow multiple opReturn outputs (they are non-standard but to be safe lets check it)
        long numOpReturnOutputs = tempTx.getTempTxOutputs().stream().filter(txOutputParser::isOpReturnOutput).count();
        if (numOpReturnOutputs <= 1) {
            boolean isAnyTxOutputTypeUndefined = tempTx.getTempTxOutputs().stream().anyMatch(txOutput -> TxOutputType.UNDEFINED == txOutput.getTxOutputType());
            if (!isAnyTxOutputTypeUndefined) {
                // TODO(chirhonul): we don't modify the tempTx within the call below, so maybe we should
                // use RawTx?
                TxType txType = TxParser.getBisqTxType(tempTx, txOutputParser.getOptionalOpReturnTypeCandidate().isPresent(), remainingInputValue, getOptionalOpReturnType());
                tempTx.setTxType(txType);
                if (remainingInputValue > 0)
                    tempTx.setBurntFee(remainingInputValue);
            } else {
                tempTx.setTxType(TxType.INVALID);
                String msg = "We have undefined txOutput types which must not happen. tx=" + tempTx;
                DevEnv.logErrorAndThrowIfDevMode(msg);
            }
        } else {
            // We don't consider a tx with multiple OpReturn outputs valid.
            tempTx.setTxType(TxType.INVALID);
            String msg = "Invalid tx. We have multiple opReturn outputs. tx=" + tempTx;
            log.warn(msg);
        }
    }
    if (hasBsqInputs || txInputParser.getBurntBondValue() > 0)
        return Optional.of(Tx.fromTempTx(tempTx));
    else
        return Optional.empty();
}
Also used : TempTx(bisq.core.dao.state.blockchain.TempTx) TxType(bisq.core.dao.state.blockchain.TxType) TempTxOutput(bisq.core.dao.state.blockchain.TempTxOutput) TxOutputKey(bisq.core.dao.state.blockchain.TxOutputKey) TxInput(bisq.core.dao.state.blockchain.TxInput)

Aggregations

TempTx (bisq.core.dao.state.blockchain.TempTx)4 TxInput (bisq.core.dao.state.blockchain.TxInput)3 InvalidGenesisTxException (bisq.core.dao.node.parser.exceptions.InvalidGenesisTxException)2 RawTx (bisq.core.dao.state.blockchain.RawTx)2 RawTxOutput (bisq.core.dao.state.blockchain.RawTxOutput)2 TempTxOutput (bisq.core.dao.state.blockchain.TempTxOutput)2 TxType (bisq.core.dao.state.blockchain.TxType)2 Test (org.junit.Test)2 OpReturnType (bisq.core.dao.state.blockchain.OpReturnType)1 TxOutputKey (bisq.core.dao.state.blockchain.TxOutputKey)1 Coin (org.bitcoinj.core.Coin)1