Search in sources :

Example 21 with TransactionInput

use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.

the class BtcWalletService method doubleSpendTransaction.

// /////////////////////////////////////////////////////////////////////////////////////////
// Double spend unconfirmed transaction (unlock in case we got into a tx with a too low mining fee)
// /////////////////////////////////////////////////////////////////////////////////////////
public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientFundsException {
    AddressEntry addressEntry = getOrCreateUnusedAddressEntry(AddressEntry.Context.AVAILABLE);
    checkNotNull(addressEntry.getAddress(), "addressEntry.getAddress() must not be null");
    Optional<Transaction> transactionOptional = wallet.getTransactions(true).stream().filter(t -> t.getHashAsString().equals(txId)).findAny();
    if (transactionOptional.isPresent()) {
        Transaction txToDoubleSpend = transactionOptional.get();
        Address toAddress = addressEntry.getAddress();
        final TransactionConfidence.ConfidenceType confidenceType = txToDoubleSpend.getConfidence().getConfidenceType();
        if (confidenceType == TransactionConfidence.ConfidenceType.PENDING) {
            log.debug("txToDoubleSpend no. of inputs " + txToDoubleSpend.getInputs().size());
            Transaction newTransaction = new Transaction(params);
            txToDoubleSpend.getInputs().stream().forEach(input -> {
                final TransactionOutput connectedOutput = input.getConnectedOutput();
                if (connectedOutput != null && connectedOutput.isMine(wallet) && connectedOutput.getParentTransaction() != null && connectedOutput.getParentTransaction().getConfidence() != null && input.getValue() != null) {
                    // if (connectedOutput.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
                    newTransaction.addInput(new TransactionInput(params, newTransaction, new byte[] {}, new TransactionOutPoint(params, input.getOutpoint().getIndex(), new Transaction(params, connectedOutput.getParentTransaction().bitcoinSerialize())), Coin.valueOf(input.getValue().value)));
                /* } else {
                                    log.warn("Confidence of parent tx is not of type BUILDING: ConfidenceType=" +
                                            connectedOutput.getParentTransaction().getConfidence().getConfidenceType());
                                }*/
                }
            });
            log.info("newTransaction no. of inputs " + newTransaction.getInputs().size());
            log.info("newTransaction size in kB " + newTransaction.bitcoinSerialize().length / 1024);
            if (!newTransaction.getInputs().isEmpty()) {
                Coin amount = Coin.valueOf(newTransaction.getInputs().stream().mapToLong(input -> input.getValue() != null ? input.getValue().value : 0).sum());
                newTransaction.addOutput(amount, toAddress);
                try {
                    Coin fee;
                    int counter = 0;
                    int txSize = 0;
                    Transaction tx;
                    SendRequest sendRequest;
                    Coin txFeeForWithdrawalPerByte = getTxFeeForWithdrawalPerByte();
                    do {
                        counter++;
                        fee = txFeeForWithdrawalPerByte.multiply(txSize);
                        newTransaction.clearOutputs();
                        newTransaction.addOutput(amount.subtract(fee), toAddress);
                        sendRequest = SendRequest.forTx(newTransaction);
                        sendRequest.fee = fee;
                        sendRequest.feePerKb = Coin.ZERO;
                        sendRequest.ensureMinRequiredFee = false;
                        sendRequest.aesKey = aesKey;
                        sendRequest.coinSelector = new BtcCoinSelector(toAddress);
                        sendRequest.changeAddress = toAddress;
                        wallet.completeTx(sendRequest);
                        tx = sendRequest.tx;
                        txSize = tx.bitcoinSerialize().length;
                        printTx("FeeEstimationTransaction", tx);
                        sendRequest.tx.getOutputs().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
                    } while (feeEstimationNotSatisfied(counter, tx));
                    if (counter == 10)
                        log.error("Could not calculate the fee. Tx=" + tx);
                    Wallet.SendResult sendResult = null;
                    try {
                        sendRequest = SendRequest.forTx(newTransaction);
                        sendRequest.fee = fee;
                        sendRequest.feePerKb = Coin.ZERO;
                        sendRequest.ensureMinRequiredFee = false;
                        sendRequest.aesKey = aesKey;
                        sendRequest.coinSelector = new BtcCoinSelector(toAddress);
                        sendRequest.changeAddress = toAddress;
                        sendResult = wallet.sendCoins(sendRequest);
                    } catch (InsufficientMoneyException e) {
                        // in some cases getFee did not calculate correctly and we still get an InsufficientMoneyException
                        log.warn("We still have a missing fee " + (e.missing != null ? e.missing.toFriendlyString() : ""));
                        amount = amount.subtract(e.missing);
                        newTransaction.clearOutputs();
                        newTransaction.addOutput(amount, toAddress);
                        sendRequest = SendRequest.forTx(newTransaction);
                        sendRequest.fee = fee;
                        sendRequest.feePerKb = Coin.ZERO;
                        sendRequest.ensureMinRequiredFee = false;
                        sendRequest.aesKey = aesKey;
                        sendRequest.coinSelector = new BtcCoinSelector(toAddress, false);
                        sendRequest.changeAddress = toAddress;
                        try {
                            sendResult = wallet.sendCoins(sendRequest);
                            printTx("FeeEstimationTransaction", newTransaction);
                        } catch (InsufficientMoneyException e2) {
                            errorMessageHandler.handleErrorMessage("We did not get the correct fee calculated. " + (e2.missing != null ? e2.missing.toFriendlyString() : ""));
                        }
                    }
                    if (sendResult != null) {
                        log.info("Broadcasting double spending transaction. " + sendResult.tx);
                        Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {

                            @Override
                            public void onSuccess(Transaction result) {
                                log.info("Double spending transaction published. " + result);
                                resultHandler.run();
                            }

                            @Override
                            public void onFailure(@NotNull Throwable t) {
                                log.error("Broadcasting double spending transaction failed. " + t.getMessage());
                                errorMessageHandler.handleErrorMessage(t.getMessage());
                            }
                        });
                    }
                } catch (InsufficientMoneyException e) {
                    throw new InsufficientFundsException("The fees for that transaction exceed the available funds " + "or the resulting output value is below the min. dust value:\n" + "Missing " + (e.missing != null ? e.missing.toFriendlyString() : "null"));
                }
            } else {
                String errorMessage = "We could not find inputs we control in the transaction we want to double spend.";
                log.warn(errorMessage);
                errorMessageHandler.handleErrorMessage(errorMessage);
            }
        } else if (confidenceType == TransactionConfidence.ConfidenceType.BUILDING) {
            errorMessageHandler.handleErrorMessage("That transaction is already in the blockchain so we cannot double spend it.");
        } else if (confidenceType == TransactionConfidence.ConfidenceType.DEAD) {
            errorMessageHandler.handleErrorMessage("One of the inputs of that transaction has been already double spent.");
        }
    }
}
Also used : Arrays(java.util.Arrays) Transaction(org.bitcoinj.core.Transaction) TransactionConfidence(org.bitcoinj.core.TransactionConfidence) Coin(org.bitcoinj.core.Coin) Wallet(org.bitcoinj.wallet.Wallet) LoggerFactory(org.slf4j.LoggerFactory) ArrayList(java.util.ArrayList) Inject(javax.inject.Inject) Preconditions.checkArgument(com.google.common.base.Preconditions.checkArgument) TransactionVerificationException(bisq.core.btc.exceptions.TransactionVerificationException) ImmutableList(com.google.common.collect.ImmutableList) AddressEntryList(bisq.core.btc.AddressEntryList) SendRequest(org.bitcoinj.wallet.SendRequest) ErrorMessageHandler(bisq.common.handlers.ErrorMessageHandler) KeyCrypterScrypt(org.bitcoinj.crypto.KeyCrypterScrypt) KeyParameter(org.spongycastle.crypto.params.KeyParameter) DeterministicKey(org.bitcoinj.crypto.DeterministicKey) Nullable(javax.annotation.Nullable) ScriptBuilder(org.bitcoinj.script.ScriptBuilder) AddressFormatException(org.bitcoinj.core.AddressFormatException) AddressEntryException(bisq.core.btc.AddressEntryException) WalletException(bisq.core.btc.exceptions.WalletException) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) Logger(org.slf4j.Logger) InsufficientFundsException(bisq.core.btc.InsufficientFundsException) Preconditions.checkNotNull(com.google.common.base.Preconditions.checkNotNull) Set(java.util.Set) InsufficientMoneyException(org.bitcoinj.core.InsufficientMoneyException) Collectors(java.util.stream.Collectors) FutureCallback(com.google.common.util.concurrent.FutureCallback) Futures(com.google.common.util.concurrent.Futures) List(java.util.List) AddressEntry(bisq.core.btc.AddressEntry) TransactionInput(org.bitcoinj.core.TransactionInput) Preferences(bisq.core.user.Preferences) TransactionOutput(org.bitcoinj.core.TransactionOutput) Optional(java.util.Optional) FeeService(bisq.core.provider.fee.FeeService) Address(org.bitcoinj.core.Address) Preconditions(com.google.common.base.Preconditions) NotNull(org.jetbrains.annotations.NotNull) Restrictions(bisq.core.btc.Restrictions) TransactionOutput(org.bitcoinj.core.TransactionOutput) SendRequest(org.bitcoinj.wallet.SendRequest) Address(org.bitcoinj.core.Address) AddressEntry(bisq.core.btc.AddressEntry) Wallet(org.bitcoinj.wallet.Wallet) InsufficientMoneyException(org.bitcoinj.core.InsufficientMoneyException) TransactionInput(org.bitcoinj.core.TransactionInput) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) Coin(org.bitcoinj.core.Coin) Transaction(org.bitcoinj.core.Transaction) InsufficientFundsException(bisq.core.btc.InsufficientFundsException) TransactionConfidence(org.bitcoinj.core.TransactionConfidence) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint)

Example 22 with TransactionInput

use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.

the class BtcWalletService method completePreparedBsqTx.

public Transaction completePreparedBsqTx(Transaction preparedBsqTx, boolean useCustomTxFee, @Nullable byte[] opReturnData) throws TransactionVerificationException, WalletException, InsufficientMoneyException {
    // preparedBsqTx has following structure:
    // inputs [1-n] BSQ inputs
    // outputs [0-1] BSQ receivers output
    // outputs [0-1] BSQ change output
    // mining fee: optional burned BSQ fee (only if opReturnData != null)
    // We add BTC mining fee. Result tx looks like:
    // inputs [1-n] BSQ inputs
    // inputs [1-n] BTC inputs
    // outputs [0-1] BSQ receivers output
    // outputs [0-1] BSQ change output
    // outputs [0-1] BTC change output
    // outputs [0-1] OP_RETURN with opReturnData (only if opReturnData != null)
    // mining fee: BTC mining fee + optional burned BSQ fee (only if opReturnData != null)
    // In case of txs for burned BSQ fees we have no receiver output and it might be that there is no change outputs
    // We need to guarantee that min. 1 valid output is added (OP_RETURN does not count). So we use a higher input
    // for BTC to force an additional change output.
    // safety check counter to avoid endless loops
    int counter = 0;
    // estimated size of input sig
    final int sigSizePerInput = 106;
    // typical size for a tx with 2 inputs
    int txSizeWithUnsignedInputs = 203;
    // If useCustomTxFee we allow overriding the estimated fee from preferences
    final Coin txFeePerByte = useCustomTxFee ? getTxFeeForWithdrawalPerByte() : feeService.getTxFeePerByte();
    // In case there are no change outputs we force a change by adding min dust to the BTC input
    Coin forcedChangeValue = Coin.ZERO;
    Address changeAddress = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
    checkNotNull(changeAddress, "changeAddress must not be null");
    final BtcCoinSelector coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
    final List<TransactionInput> preparedBsqTxInputs = preparedBsqTx.getInputs();
    final List<TransactionOutput> preparedBsqTxOutputs = preparedBsqTx.getOutputs();
    // We add 1 for the BTC fee input
    int numInputs = preparedBsqTxInputs.size() + 1;
    Transaction resultTx = null;
    boolean isFeeOutsideTolerance;
    boolean opReturnIsOnlyOutput;
    do {
        counter++;
        if (counter >= 10) {
            checkNotNull(resultTx, "resultTx must not be null");
            log.error("Could not calculate the fee. Tx=" + resultTx);
            break;
        }
        Transaction tx = new Transaction(params);
        preparedBsqTxInputs.stream().forEach(tx::addInput);
        if (forcedChangeValue.isZero()) {
            preparedBsqTxOutputs.stream().forEach(tx::addOutput);
        } else {
            // TODO test that case
            checkArgument(preparedBsqTxOutputs.size() == 0, "preparedBsqTxOutputs.size must be null in that code branch");
            tx.addOutput(forcedChangeValue, changeAddress);
        }
        SendRequest sendRequest = SendRequest.forTx(tx);
        sendRequest.shuffleOutputs = false;
        sendRequest.aesKey = aesKey;
        // signInputs needs to be false as it would try to sign all inputs (BSQ inputs are not in this wallet)
        sendRequest.signInputs = false;
        sendRequest.fee = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs);
        sendRequest.feePerKb = Coin.ZERO;
        sendRequest.ensureMinRequiredFee = false;
        sendRequest.coinSelector = coinSelector;
        sendRequest.changeAddress = changeAddress;
        wallet.completeTx(sendRequest);
        resultTx = sendRequest.tx;
        // We might have the rare case that both inputs matched the required fees, so both did not require
        // a change output.
        // In such cases we need to add artificially a change output (OP_RETURN is not allowed as only output)
        opReturnIsOnlyOutput = resultTx.getOutputs().size() == 0;
        forcedChangeValue = opReturnIsOnlyOutput ? Restrictions.getMinNonDustOutput() : Coin.ZERO;
        // add OP_RETURN output
        if (opReturnData != null)
            resultTx.addOutput(new TransactionOutput(params, resultTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(opReturnData).getProgram()));
        numInputs = resultTx.getInputs().size();
        txSizeWithUnsignedInputs = resultTx.bitcoinSerialize().length;
        final long estimatedFeeAsLong = txFeePerByte.multiply(txSizeWithUnsignedInputs + sigSizePerInput * numInputs).value;
        // calculated fee must be inside of a tolerance range with tx fee
        isFeeOutsideTolerance = Math.abs(resultTx.getFee().value - estimatedFeeAsLong) > 1000;
    } while (opReturnIsOnlyOutput || isFeeOutsideTolerance || resultTx.getFee().value < txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value);
    // Sign all BTC inputs
    for (int i = preparedBsqTxInputs.size(); i < resultTx.getInputs().size(); i++) {
        TransactionInput txIn = resultTx.getInputs().get(i);
        checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet), "txIn.getConnectedOutput() is not in our wallet. That must not happen.");
        signTransactionInput(wallet, aesKey, resultTx, txIn, i);
        checkScriptSig(resultTx, txIn, i);
    }
    checkWalletConsistency(wallet);
    verifyTransaction(resultTx);
    printTx("BTC wallet: Signed tx", resultTx);
    return resultTx;
}
Also used : TransactionOutput(org.bitcoinj.core.TransactionOutput) SendRequest(org.bitcoinj.wallet.SendRequest) Address(org.bitcoinj.core.Address) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) TransactionInput(org.bitcoinj.core.TransactionInput) Coin(org.bitcoinj.core.Coin) Transaction(org.bitcoinj.core.Transaction)

Example 23 with TransactionInput

use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.

the class TradeWalletService method completeBsqTradingFeeTx.

public Transaction completeBsqTradingFeeTx(Transaction preparedBsqTx, Address fundingAddress, Address reservedForTradeAddress, Address changeAddress, Coin reservedFundsForOffer, boolean useSavingsWallet, Coin txFee) throws TransactionVerificationException, WalletException, InsufficientMoneyException, AddressFormatException {
    log.debug("preparedBsqTx " + preparedBsqTx.toString());
    log.debug("fundingAddress " + fundingAddress.toString());
    log.debug("changeAddress " + changeAddress.toString());
    log.debug("reservedFundsForOffer " + reservedFundsForOffer.toPlainString());
    log.debug("useSavingsWallet " + useSavingsWallet);
    log.debug("txFee " + txFee.toPlainString());
    // preparedBsqTx has following structure:
    // inputs [1-n] BSQ inputs
    // outputs [0-1] BSQ change output
    // mining fee: burned BSQ fee
    // We add BTC mining fee. Result tx looks like:
    // inputs [1-n] BSQ inputs
    // inputs [1-n] BTC inputs
    // outputs [0-1] BSQ change output
    // outputs [1] BTC reservedForTrade output
    // outputs [0-1] BTC change output
    // mining fee: BTC mining fee + burned BSQ fee
    // In case of txs for burned BSQ fees we have no receiver output and it might be that there is no change outputs
    // We need to guarantee that min. 1 valid output is added (OP_RETURN does not count). So we use a higher input
    // for BTC to force an additional change output.
    final int preparedBsqTxInputsSize = preparedBsqTx.getInputs().size();
    // the reserved amount we need for the trade we send to our trade reservedForTradeAddress
    preparedBsqTx.addOutput(reservedFundsForOffer, reservedForTradeAddress);
    // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to
    // wait for 1 confirmation)
    // In case of double spend we will detect later in the trade process and use a ban score to penalize bad behaviour (not impl. yet)
    // WalletService.printTx("preparedBsqTx", preparedBsqTx);
    SendRequest sendRequest = SendRequest.forTx(preparedBsqTx);
    sendRequest.shuffleOutputs = false;
    sendRequest.aesKey = aesKey;
    if (useSavingsWallet)
        sendRequest.coinSelector = new BtcCoinSelector(walletsSetup.getAddressesByContext(AddressEntry.Context.AVAILABLE));
    else
        sendRequest.coinSelector = new BtcCoinSelector(fundingAddress);
    // We use a fixed fee
    sendRequest.fee = txFee;
    sendRequest.feePerKb = Coin.ZERO;
    sendRequest.ensureMinRequiredFee = false;
    sendRequest.signInputs = false;
    // Change is optional in case of overpay or use of funds from savings wallet
    sendRequest.changeAddress = changeAddress;
    checkNotNull(wallet, "Wallet must not be null");
    wallet.completeTx(sendRequest);
    Transaction resultTx = sendRequest.tx;
    // Sign all BTC inputs
    for (int i = preparedBsqTxInputsSize; i < resultTx.getInputs().size(); i++) {
        TransactionInput txIn = resultTx.getInputs().get(i);
        checkArgument(txIn.getConnectedOutput() != null && txIn.getConnectedOutput().isMine(wallet), "txIn.getConnectedOutput() is not in our wallet. That must not happen.");
        WalletService.signTransactionInput(wallet, aesKey, resultTx, txIn, i);
        WalletService.checkScriptSig(resultTx, txIn, i);
    }
    WalletService.checkWalletConsistency(wallet);
    WalletService.verifyTransaction(resultTx);
    WalletService.printTx(Res.getBaseCurrencyCode() + " wallet: Signed tx", resultTx);
    return resultTx;
}
Also used : SendRequest(org.bitcoinj.wallet.SendRequest) Transaction(org.bitcoinj.core.Transaction) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) RawTransactionInput(bisq.core.btc.data.RawTransactionInput) TransactionInput(org.bitcoinj.core.TransactionInput)

Example 24 with TransactionInput

use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.

the class TradeWalletService method makerCreatesAndSignsDepositTx.

/**
 * The maker creates the deposit transaction using the takers input(s) and optional output and signs his input(s).
 *
 * @param makerIsBuyer              The flag indicating if we are in the maker as buyer role or the opposite.
 * @param contractHash              The hash of the contract to be added to the OP_RETURN output.
 * @param makerInputAmount          The input amount of the maker.
 * @param msOutputAmount            The output amount to our MS output.
 * @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input)
 * @param takerChangeOutputValue    Optional taker change output value
 * @param takerChangeAddressString  Optional taker change address
 * @param makerAddress              The maker's address.
 * @param makerChangeAddress        The maker's change address.
 * @param buyerPubKey               The public key of the buyer.
 * @param sellerPubKey              The public key of the seller.
 * @param arbitratorPubKey          The public key of the arbitrator.
 * @return A data container holding the serialized transaction and the maker raw inputs
 * @throws SigningException
 * @throws TransactionVerificationException
 * @throws WalletException
 */
public PreparedDepositTxAndMakerInputs makerCreatesAndSignsDepositTx(boolean makerIsBuyer, byte[] contractHash, Coin makerInputAmount, Coin msOutputAmount, List<RawTransactionInput> takerRawTransactionInputs, long takerChangeOutputValue, @Nullable String takerChangeAddressString, Address makerAddress, Address makerChangeAddress, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) throws SigningException, TransactionVerificationException, WalletException, AddressFormatException {
    log.debug("makerCreatesAndSignsDepositTx called");
    log.debug("makerIsBuyer " + makerIsBuyer);
    log.debug("makerInputAmount " + makerInputAmount.toFriendlyString());
    log.debug("msOutputAmount " + msOutputAmount.toFriendlyString());
    log.debug("takerRawInputs " + takerRawTransactionInputs.toString());
    log.debug("takerChangeOutputValue " + takerChangeOutputValue);
    log.debug("takerChangeAddressString " + takerChangeAddressString);
    log.debug("makerAddress " + makerAddress);
    log.debug("makerChangeAddress " + makerChangeAddress);
    log.debug("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
    log.debug("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
    log.debug("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
    checkArgument(!takerRawTransactionInputs.isEmpty());
    // First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx.
    // Similar to the way we did in the createTakerDepositTxInputs method.
    Transaction dummyTx = new Transaction(params);
    TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, makerInputAmount, new ECKey().toAddress(params));
    dummyTx.addOutput(dummyOutput);
    addAvailableInputsAndChangeOutputs(dummyTx, makerAddress, makerChangeAddress, Coin.ZERO);
    // Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions.
    List<TransactionInput> makerInputs = dummyTx.getInputs();
    TransactionOutput makerOutput = null;
    // We don't support more then 1 optional change output
    checkArgument(dummyTx.getOutputs().size() < 3, "dummyTx.getOutputs().size() >= 3");
    // Only save change outputs, the dummy output is ignored (that's why we start with index 1)
    if (dummyTx.getOutputs().size() > 1)
        makerOutput = dummyTx.getOutput(1);
    // Now we construct the real deposit tx
    Transaction preparedDepositTx = new Transaction(params);
    ArrayList<RawTransactionInput> makerRawTransactionInputs = new ArrayList<>();
    if (makerIsBuyer) {
        // Add buyer inputs
        for (TransactionInput input : makerInputs) {
            preparedDepositTx.addInput(input);
            makerRawTransactionInputs.add(getRawInputFromTransactionInput(input));
        }
        // the sellers input is not signed so we attach empty script bytes
        for (RawTransactionInput rawTransactionInput : takerRawTransactionInputs) preparedDepositTx.addInput(getTransactionInput(preparedDepositTx, new byte[] {}, rawTransactionInput));
    } else {
        // the sellers input is not signed so we attach empty script bytes
        for (RawTransactionInput rawTransactionInput : takerRawTransactionInputs) preparedDepositTx.addInput(getTransactionInput(preparedDepositTx, new byte[] {}, rawTransactionInput));
        // Add seller inputs
        for (TransactionInput input : makerInputs) {
            preparedDepositTx.addInput(input);
            makerRawTransactionInputs.add(getRawInputFromTransactionInput(input));
        }
    }
    // Add MultiSig output
    Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
    // Tx fee for deposit tx will be paid by buyer.
    TransactionOutput p2SHMultiSigOutput = new TransactionOutput(params, preparedDepositTx, msOutputAmount, p2SHMultiSigOutputScript.getProgram());
    preparedDepositTx.addOutput(p2SHMultiSigOutput);
    // We add the hash ot OP_RETURN with a 0 amount output
    TransactionOutput contractHashOutput = new TransactionOutput(params, preparedDepositTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(contractHash).getProgram());
    preparedDepositTx.addOutput(contractHashOutput);
    TransactionOutput takerTransactionOutput = null;
    if (takerChangeOutputValue > 0 && takerChangeAddressString != null)
        takerTransactionOutput = new TransactionOutput(params, preparedDepositTx, Coin.valueOf(takerChangeOutputValue), Address.fromBase58(params, takerChangeAddressString));
    if (makerIsBuyer) {
        // Add optional buyer outputs
        if (makerOutput != null)
            preparedDepositTx.addOutput(makerOutput);
        // Add optional seller outputs
        if (takerTransactionOutput != null)
            preparedDepositTx.addOutput(takerTransactionOutput);
    } else {
        // Add optional seller outputs
        if (takerTransactionOutput != null)
            preparedDepositTx.addOutput(takerTransactionOutput);
        // Add optional buyer outputs
        if (makerOutput != null)
            preparedDepositTx.addOutput(makerOutput);
    }
    // Sign inputs
    int start = makerIsBuyer ? 0 : takerRawTransactionInputs.size();
    int end = makerIsBuyer ? makerInputs.size() : preparedDepositTx.getInputs().size();
    for (int i = start; i < end; i++) {
        TransactionInput input = preparedDepositTx.getInput(i);
        signInput(preparedDepositTx, input, i);
        WalletService.checkScriptSig(preparedDepositTx, input, i);
    }
    WalletService.printTx("prepared depositTx", preparedDepositTx);
    WalletService.verifyTransaction(preparedDepositTx);
    return new PreparedDepositTxAndMakerInputs(makerRawTransactionInputs, preparedDepositTx.bitcoinSerialize());
}
Also used : Script(org.bitcoinj.script.Script) TransactionOutput(org.bitcoinj.core.TransactionOutput) Transaction(org.bitcoinj.core.Transaction) RawTransactionInput(bisq.core.btc.data.RawTransactionInput) ArrayList(java.util.ArrayList) ECKey(org.bitcoinj.core.ECKey) RawTransactionInput(bisq.core.btc.data.RawTransactionInput) TransactionInput(org.bitcoinj.core.TransactionInput) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) PreparedDepositTxAndMakerInputs(bisq.core.btc.data.PreparedDepositTxAndMakerInputs)

Example 25 with TransactionInput

use of org.bitcoinj.core.TransactionInput in project bisq-core by bisq-network.

the class TradeWalletService method takerSignsAndPublishesDepositTx.

/**
 * The taker signs the deposit transaction he received from the maker and publishes it.
 *
 * @param takerIsSeller             The flag indicating if we are in the taker as seller role or the opposite.
 * @param contractHash              The hash of the contract to be added to the OP_RETURN output.
 * @param makersDepositTxSerialized The prepared deposit transaction signed by the maker.
 * @param buyerInputs               The connected outputs for all inputs of the buyer.
 * @param sellerInputs              The connected outputs for all inputs of the seller.
 * @param buyerPubKey               The public key of the buyer.
 * @param sellerPubKey              The public key of the seller.
 * @param arbitratorPubKey          The public key of the arbitrator.
 * @param callback                  Callback when transaction is broadcasted.
 * @throws SigningException
 * @throws TransactionVerificationException
 * @throws WalletException
 */
public Transaction takerSignsAndPublishesDepositTx(boolean takerIsSeller, byte[] contractHash, byte[] makersDepositTxSerialized, List<RawTransactionInput> buyerInputs, List<RawTransactionInput> sellerInputs, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey, FutureCallback<Transaction> callback) throws SigningException, TransactionVerificationException, WalletException {
    Transaction makersDepositTx = new Transaction(params, makersDepositTxSerialized);
    log.debug("signAndPublishDepositTx called");
    log.debug("takerIsSeller " + takerIsSeller);
    log.debug("makersDepositTx " + makersDepositTx.toString());
    log.debug("buyerConnectedOutputsForAllInputs " + buyerInputs.toString());
    log.debug("sellerConnectedOutputsForAllInputs " + sellerInputs.toString());
    log.debug("buyerPubKey " + ECKey.fromPublicOnly(buyerPubKey).toString());
    log.debug("sellerPubKey " + ECKey.fromPublicOnly(sellerPubKey).toString());
    log.debug("arbitratorPubKey " + ECKey.fromPublicOnly(arbitratorPubKey).toString());
    checkArgument(!buyerInputs.isEmpty());
    checkArgument(!sellerInputs.isEmpty());
    // Check if maker's Multisig script is identical to the takers
    Script p2SHMultiSigOutputScript = getP2SHMultiSigOutputScript(buyerPubKey, sellerPubKey, arbitratorPubKey);
    if (!makersDepositTx.getOutput(0).getScriptPubKey().equals(p2SHMultiSigOutputScript))
        throw new TransactionVerificationException("Maker's p2SHMultiSigOutputScript does not match to takers p2SHMultiSigOutputScript");
    // The outpoints are not available from the serialized makersDepositTx, so we cannot use that tx directly, but we use it to construct a new
    // depositTx
    Transaction depositTx = new Transaction(params);
    if (takerIsSeller) {
        // We grab the signature from the makersDepositTx and apply it to the new tx input
        for (int i = 0; i < buyerInputs.size(); i++) depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), buyerInputs.get(i)));
        // Add seller inputs
        for (RawTransactionInput rawTransactionInput : sellerInputs) depositTx.addInput(getTransactionInput(depositTx, new byte[] {}, rawTransactionInput));
    } else {
        // Add buyer inputs and apply signature
        for (RawTransactionInput rawTransactionInput : buyerInputs) depositTx.addInput(getTransactionInput(depositTx, new byte[] {}, rawTransactionInput));
        // We grab the signature from the makersDepositTx and apply it to the new tx input
        for (int i = buyerInputs.size(), k = 0; i < makersDepositTx.getInputs().size(); i++, k++) depositTx.addInput(getTransactionInput(depositTx, getScriptProgram(makersDepositTx, i), sellerInputs.get(k)));
    }
    // Check if OP_RETURN output with contract hash matches the one from the maker
    TransactionOutput contractHashOutput = new TransactionOutput(params, makersDepositTx, Coin.ZERO, ScriptBuilder.createOpReturnScript(contractHash).getProgram());
    log.debug("contractHashOutput " + contractHashOutput);
    TransactionOutput makersContractHashOutput = makersDepositTx.getOutputs().get(1);
    log.debug("makersContractHashOutput " + makersContractHashOutput);
    if (!makersContractHashOutput.getScriptPubKey().equals(contractHashOutput.getScriptPubKey()))
        throw new TransactionVerificationException("Maker's transaction output for the contract hash is not matching takers version.");
    // Add all outputs from makersDepositTx to depositTx
    makersDepositTx.getOutputs().forEach(depositTx::addOutput);
    // WalletService.printTx("makersDepositTx", makersDepositTx);
    // Sign inputs
    int start = takerIsSeller ? buyerInputs.size() : 0;
    int end = takerIsSeller ? depositTx.getInputs().size() : buyerInputs.size();
    for (int i = start; i < end; i++) {
        TransactionInput input = depositTx.getInput(i);
        signInput(depositTx, input, i);
        WalletService.checkScriptSig(depositTx, input, i);
    }
    WalletService.printTx("depositTx", depositTx);
    WalletService.verifyTransaction(depositTx);
    WalletService.checkWalletConsistency(wallet);
    broadcastTx(depositTx, callback);
    return depositTx;
}
Also used : Script(org.bitcoinj.script.Script) TransactionOutput(org.bitcoinj.core.TransactionOutput) Transaction(org.bitcoinj.core.Transaction) TransactionVerificationException(bisq.core.btc.exceptions.TransactionVerificationException) RawTransactionInput(bisq.core.btc.data.RawTransactionInput) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) RawTransactionInput(bisq.core.btc.data.RawTransactionInput) TransactionInput(org.bitcoinj.core.TransactionInput)

Aggregations

TransactionInput (org.bitcoinj.core.TransactionInput)36 Transaction (org.bitcoinj.core.Transaction)29 TransactionOutput (org.bitcoinj.core.TransactionOutput)20 TransactionOutPoint (org.bitcoinj.core.TransactionOutPoint)18 Script (org.bitcoinj.script.Script)16 ECKey (org.bitcoinj.core.ECKey)11 HashMap (java.util.HashMap)10 Address (org.bitcoinj.core.Address)9 Coin (org.bitcoinj.core.Coin)9 ArrayList (java.util.ArrayList)7 AddressFormatException (org.bitcoinj.core.AddressFormatException)7 RawTransactionInput (bisq.core.btc.data.RawTransactionInput)6 TransactionSignature (org.bitcoinj.crypto.TransactionSignature)6 ScriptException (org.bitcoinj.script.ScriptException)6 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)5 IOException (java.io.IOException)5 Sha256Hash (org.bitcoinj.core.Sha256Hash)5 MnemonicException (org.bitcoinj.crypto.MnemonicException)5 SendRequest (org.bitcoinj.wallet.SendRequest)5 BlockedUTXO (com.samourai.wallet.send.BlockedUTXO)4