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.");
}
}
}
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;
}
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;
}
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());
}
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;
}
Aggregations