Search in sources :

Example 6 with TxInput

use of bisq.core.dao.state.blockchain.TxInput 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 7 with TxInput

use of bisq.core.dao.state.blockchain.TxInput 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)

Example 8 with TxInput

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

the class IssuanceService method issueBsq.

public void issueBsq(CompensationProposal compensationProposal, int chainHeight) {
    bsqStateService.getIssuanceCandidateTxOutputs().stream().filter(txOutput -> isValid(txOutput, compensationProposal, periodService, chainHeight)).forEach(txOutput -> {
        // We don't check atm if the output is unspent. We cannot use the bsqWallet as that would not
        // reflect our current block state (could have been spent at later block which is valid and
        // bsqWallet would show that spent state). We would need to support a spent status for the outputs
        // which are interpreted as BTC (as a not yet accepted comp. request).
        Optional<Tx> optionalTx = bsqStateService.getTx(compensationProposal.getTxId());
        if (optionalTx.isPresent()) {
            long amount = compensationProposal.getRequestedBsq().value;
            Tx tx = optionalTx.get();
            // We use key from first input
            TxInput txInput = tx.getTxInputs().get(0);
            String pubKey = txInput.getPubKey();
            Issuance issuance = new Issuance(tx.getId(), chainHeight, amount, pubKey);
            bsqStateService.addIssuance(issuance);
            bsqStateService.addUnspentTxOutput(txOutput);
            StringBuilder sb = new StringBuilder();
            sb.append("\n################################################################################\n");
            sb.append("We issued new BSQ to tx with ID ").append(txOutput.getTxId()).append("\nfor compensationProposal with UID ").append(compensationProposal.getTxId()).append("\n################################################################################\n");
            log.info(sb.toString());
        } else {
            // TODO throw exception
            log.error("Tx for compensation request not found. txId={}", compensationProposal.getTxId());
        }
    });
}
Also used : Inject(javax.inject.Inject) Issuance(bisq.core.dao.state.governance.Issuance) Slf4j(lombok.extern.slf4j.Slf4j) PeriodService(bisq.core.dao.state.period.PeriodService) TxOutput(bisq.core.dao.state.blockchain.TxOutput) Tx(bisq.core.dao.state.blockchain.Tx) BsqStateService(bisq.core.dao.state.BsqStateService) Optional(java.util.Optional) CompensationProposal(bisq.core.dao.governance.proposal.compensation.CompensationProposal) DaoPhase(bisq.core.dao.state.period.DaoPhase) TxInput(bisq.core.dao.state.blockchain.TxInput) Tx(bisq.core.dao.state.blockchain.Tx) Issuance(bisq.core.dao.state.governance.Issuance) TxInput(bisq.core.dao.state.blockchain.TxInput)

Aggregations

TxInput (bisq.core.dao.state.blockchain.TxInput)8 RawTx (bisq.core.dao.state.blockchain.RawTx)5 RawTxOutput (bisq.core.dao.state.blockchain.RawTxOutput)5 Test (org.junit.Test)4 TempTx (bisq.core.dao.state.blockchain.TempTx)3 TxOutput (bisq.core.dao.state.blockchain.TxOutput)2 TxOutputKey (bisq.core.dao.state.blockchain.TxOutputKey)2 TxType (bisq.core.dao.state.blockchain.TxType)2 RawTransaction (com.neemre.btcdcli4j.core.domain.RawTransaction)2 Date (java.util.Date)2 Slf4j (lombok.extern.slf4j.Slf4j)2 Coin (org.bitcoinj.core.Coin)2 UserThread (bisq.common.UserThread)1 ResultHandler (bisq.common.handlers.ResultHandler)1 Utilities (bisq.common.util.Utilities)1 DaoOptionKeys (bisq.core.dao.DaoOptionKeys)1 CompensationProposal (bisq.core.dao.governance.proposal.compensation.CompensationProposal)1 InvalidGenesisTxException (bisq.core.dao.node.parser.exceptions.InvalidGenesisTxException)1 BsqStateService (bisq.core.dao.state.BsqStateService)1 OpReturnType (bisq.core.dao.state.blockchain.OpReturnType)1