From f0d9a5d81e4c9cd3dd2708402f754d6d61f47678 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 16 Mar 2015 23:12:31 +0100 Subject: [PATCH] Move trade tx methods to TradeService --- .../java/io/bitsquare/btc/TradeService.java | 587 ++++++++++++++++++ .../java/io/bitsquare/btc/WalletService.java | 417 +------------ .../tasks/BroadcastCreateOfferFeeTx.java | 2 +- .../placeoffer/tasks/CreateOfferFeeTx.java | 3 +- .../protocol/trade/OfferSharedModel.java | 8 + .../tasks/GetOffererDepositTxInputs.java | 4 +- .../tasks/SendBankTransferStartedMessage.java | 2 +- .../tasks/SignAndPublishDepositTx.java | 2 +- .../trade/offerer/tasks/SignPayoutTx.java | 4 +- .../trade/taker/tasks/PayTakeOfferFee.java | 4 +- .../taker/tasks/SendSignedTakerDepositTx.java | 2 +- .../taker/tasks/SignAndPublishPayoutTx.java | 2 +- .../taker/tasks/TakerCommitDepositTx.java | 2 +- .../tasks/TakerCreatesAndSignsDepositTx.java | 4 +- 14 files changed, 627 insertions(+), 416 deletions(-) create mode 100644 core/src/main/java/io/bitsquare/btc/TradeService.java diff --git a/core/src/main/java/io/bitsquare/btc/TradeService.java b/core/src/main/java/io/bitsquare/btc/TradeService.java new file mode 100644 index 0000000000..561827a920 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/TradeService.java @@ -0,0 +1,587 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.btc; + +import io.bitsquare.btc.exceptions.SigningException; +import io.bitsquare.btc.exceptions.TransactionVerificationException; +import io.bitsquare.btc.exceptions.WalletException; + +import org.bitcoinj.core.Address; +import org.bitcoinj.core.AddressFormatException; +import org.bitcoinj.core.Coin; +import org.bitcoinj.core.ECKey; +import org.bitcoinj.core.InsufficientMoneyException; +import org.bitcoinj.core.NetworkParameters; +import org.bitcoinj.core.Sha256Hash; +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionInput; +import org.bitcoinj.core.TransactionOutPoint; +import org.bitcoinj.core.TransactionOutput; +import org.bitcoinj.core.Wallet; +import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.kits.WalletAppKit; +import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptBuilder; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.google.inject.internal.util.$Preconditions.*; + +// +/* + + Deposit tx: + To keep the multiple partial deposit tx consistent with the final deposit tx used for publishing + we use always use offerers in/outputs first then takers in/outputs. + + IN[0] offerer (mandatory) e.g. 0.1 BTC + IN[...] optional additional offerer inputs (normally never used as we pay from trade fee tx and always have 1 output there) + IN[...] taker (mandatory) e.g. 1.1001 BTC + IN[...] optional additional taker inputs (normally never used as we pay from trade fee tx and always have 1 output there) + OUT[0] Multisig output (include tx fee for payout tx) e.g. 1.2001 + OUT[1] offerer change (normally never used as we pay from trade fee tx and always have 1 output there) + OUT[...] optional additional offerer outputs (supported but no use case yet for that) + OUT[...] taker change (normally never used as we pay from trade fee tx and always have 1 output there) + OUT[...] optional additional taker outputs (supported but no use case yet for that) + FEE tx fee 0.0001 BTC + + Payout tx: + IN[0] Multisig output form deposit Tx (signed by offerer and trader) + OUT[0] Offerer payout address + OUT[1] Taker payout address + + */ +public class TradeService { + private static final Logger log = LoggerFactory.getLogger(TradeService.class); + + private final NetworkParameters params; + private final Wallet wallet; + private WalletAppKit walletAppKit; + private FeePolicy feePolicy; + + public TradeService(NetworkParameters params, Wallet wallet, WalletAppKit walletAppKit, FeePolicy feePolicy) { + this.params = params; + this.wallet = wallet; + this.walletAppKit = walletAppKit; + this.feePolicy = feePolicy; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Trade fee + /////////////////////////////////////////////////////////////////////////////////////////// + + public Transaction createOfferFeeTx(AddressEntry addressEntry) throws InsufficientMoneyException { + Transaction createOfferFeeTx = new Transaction(params); + Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE); + createOfferFeeTx.addOutput(fee, feePolicy.getAddressForCreateOfferFee()); + Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(createOfferFeeTx); + sendRequest.shuffleOutputs = false; + // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to + // wait for 1 confirmation) + sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); + sendRequest.changeAddress = addressEntry.getAddress(); + wallet.completeTx(sendRequest); + printTxWithInputs("createOfferFeeTx", createOfferFeeTx); + return createOfferFeeTx; + } + + public void broadcastCreateOfferFeeTx(Transaction createOfferFeeTx, FutureCallback callback) { + ListenableFuture future = walletAppKit.peerGroup().broadcastTransaction(createOfferFeeTx); + Futures.addCallback(future, callback); + } + + public void payTakeOfferFee(AddressEntry addressEntry, FutureCallback callback) throws InsufficientMoneyException { + Transaction takeOfferFeeTx = new Transaction(params); + Coin fee = FeePolicy.TAKE_OFFER_FEE.subtract(FeePolicy.TX_FEE); + takeOfferFeeTx.addOutput(fee, feePolicy.getAddressForTakeOfferFee()); + + Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(takeOfferFeeTx); + sendRequest.shuffleOutputs = false; + // we allow spending of unconfirmed takeOfferFeeTx (double spend risk is low and usability would suffer if we need to + // wait for 1 confirmation) + sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); + sendRequest.changeAddress = addressEntry.getAddress(); + Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); + Futures.addCallback(sendResult.broadcastComplete, callback); + + printTxWithInputs("takeOfferFeeTx", takeOfferFeeTx); + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Trade + /////////////////////////////////////////////////////////////////////////////////////////// + + public TransactionDataResult offererCreatesDepositTxInputs(Coin inputAmount, AddressEntry addressInfo) throws InsufficientMoneyException, + TransactionVerificationException, WalletException { + + // We pay the tx fee 2 times to the deposit tx: + // 1. Will be spent when publishing the deposit tx (paid by offerer) + // 2. Will be added to the MS amount, so when publishing the payout tx the fee is already there and the outputs are not changed by fee reduction + // The fee for the payout will be paid by the taker. + + // inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount. + Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.TX_FEE); + + Transaction dummyTX = new Transaction(params); + // The output is just used to get the right inputs and change outputs, so we use an anonymous ECKey, as it will never be used for anything. + // We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee. + TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, new ECKey().toAddress(params)); + dummyTX.addOutput(dummyOutput); + + // Fin the needed inputs to pay the output, optional add change output. + // Normally only 1 input and no change output is used, but we support multiple inputs and outputs. Our spending transaction output is from the create + // offer fee payment. In future changes (in case of no offer fee) multiple inputs might become used. + addAvailableInputsAndChangeOutputs(dummyTX, addressInfo); + + // The completeTx() call signs the input, but we don't want to pass over signed tx inputs + // But to be safe and to support future changes (in case of no offer fee) we handle potential multiple inputs + removeSignatures(dummyTX); + + verifyTransaction(dummyTX); + checkWalletConsistency(); + + // The created tx looks like: + /* + IN[0] any input > inputAmount (including tx fee) (unsigned) + IN[1...n] optional inputs supported, but currently there is just 1 input (unsigned) + OUT[0] dummyOutputAmount (inputAmount - tx fee) + OUT[1] Optional Change = inputAmount - dummyOutputAmount - tx fee + OUT[2...n] optional more outputs are supported, but currently there is just max. 1 optional change output + */ + + printTxWithInputs("dummyTX", dummyTX); + + List connectedOutputsForAllInputs = new ArrayList<>(); + for (TransactionInput input : dummyTX.getInputs()) { + connectedOutputsForAllInputs.add(input.getConnectedOutput()); + } + + // Only save offerer outputs, the MS output is ignored + List outputs = new ArrayList<>(); + for (TransactionOutput output : dummyTX.getOutputs()) { + if (output.equals(dummyOutput)) + continue; + outputs.add(output); + } + + return new TransactionDataResult(connectedOutputsForAllInputs, outputs); + } + + public TransactionDataResult takerCreatesAndSignsDepositTx(Coin takerInputAmount, + Coin msOutputAmount, + List offererConnectedOutputsForAllInputs, + List offererOutputs, + AddressEntry addressInfo, + byte[] offererPubKey, + byte[] takerPubKey, + byte[] arbitratorPubKey) throws InsufficientMoneyException, SigningException, + TransactionVerificationException, WalletException { + + checkArgument(offererConnectedOutputsForAllInputs.size() > 0); + + // First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx. Same as in first step at offerer. + Transaction dummyTx = new Transaction(params); + Coin dummyOutputAmount = takerInputAmount.subtract(FeePolicy.TX_FEE); + TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params)); + dummyTx.addOutput(dummyOutput); + addAvailableInputsAndChangeOutputs(dummyTx, addressInfo); + List takerInputs = dummyTx.getInputs(); + List takerOutputs = new ArrayList<>(); + // we store optional change outputs (ignoring dummyOutput) + for (int i = 1; i < dummyTx.getOutputs().size(); i++) { + takerOutputs.add(dummyTx.getOutput(i)); + } + + // Now we construct real deposit tx + Transaction depositTx = new Transaction(params); + + // Add offerer inputs (normally its just 1 input) + for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { + TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); + TransactionInput transactionInput = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); + depositTx.addInput(transactionInput); + } + + // Add taker inputs + List connectedOutputsForAllTakerInputs = new ArrayList<>(); + for (TransactionInput input : takerInputs) { + depositTx.addInput(input); + connectedOutputsForAllTakerInputs.add(input.getConnectedOutput()); + } + + // Add MultiSig output + Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); + // Tx fee for deposit tx will be paid by offerer. + TransactionOutput msOutput = new TransactionOutput(params, depositTx, msOutputAmount, multiSigOutputScript.getProgram()); + depositTx.addOutput(msOutput); + + // Add optional offerer outputs + for (TransactionOutput output : offererOutputs) { + depositTx.addOutput(output); + } + + Coin takersSpendingAmount = Coin.ZERO; + + // Add optional taker outputs + for (TransactionOutput output : takerOutputs) { + depositTx.addOutput(output); + + // subtract change amount + takersSpendingAmount = takersSpendingAmount.subtract(output.getValue()); + } + + // Sign inputs + for (int i = offererConnectedOutputsForAllInputs.size(); i < depositTx.getInputs().size(); i++) { + TransactionInput input = depositTx.getInput(i); + signInput(depositTx, input, i); + checkScriptSig(depositTx, input, i); + + // add up spending amount + takersSpendingAmount = takersSpendingAmount.add(input.getConnectedOutput().getValue()); + } + + if (takerInputAmount.compareTo(takersSpendingAmount) != 0) + throw new TransactionVerificationException("Takers input amount does not match required value."); + + verifyTransaction(depositTx); + checkWalletConsistency(); + + printTxWithInputs("depositTx", depositTx); + return new TransactionDataResult(depositTx, connectedOutputsForAllTakerInputs, takerOutputs); + } + + public void offererSignsAndPublishTx(Transaction takersDepositTx, + List offererConnectedOutputsForAllInputs, + List takerConnectedOutputsForAllInputs, + List offererOutputs, + Coin offererInputAmount, + byte[] offererPubKey, + byte[] takerPubKey, + byte[] arbitratorPubKey, + FutureCallback callback) throws SigningException, TransactionVerificationException, WalletException { + + checkArgument(offererConnectedOutputsForAllInputs.size() > 0); + checkArgument(takerConnectedOutputsForAllInputs.size() > 0); + + // Check if takers Multisig script is identical to mine + Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); + if (!takersDepositTx.getOutput(0).getScriptPubKey().equals(multiSigOutputScript)) + throw new TransactionVerificationException("Takers multiSigOutputScript does not match to my multiSigOutputScript"); + + // The outpoints are not available from the serialized takersDepositTx, so we cannot use that tx directly, but we use it to construct a new depositTx + Transaction depositTx = new Transaction(params); + + // Add offerer inputs + Coin offererSpendingAmount = Coin.ZERO; + for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { + TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); + TransactionInput input = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); + depositTx.addInput(input); + + // add up spending amount + offererSpendingAmount = offererSpendingAmount.add(input.getConnectedOutput().getValue()); + } + + // Add taker inputs and apply signature + List takerInputs = new ArrayList<>(); + for (TransactionOutput connectedOutputForInput : takerConnectedOutputsForAllInputs) { + TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); + + // We grab the signature from the takersDepositTx and apply it to the new tx input + TransactionInput takerInput = takersDepositTx.getInputs().get(offererConnectedOutputsForAllInputs.size()); + byte[] scriptProgram = takerInput.getScriptSig().getProgram(); + if (scriptProgram.length == 0) + throw new TransactionVerificationException("Inputs from taker not singed."); + + TransactionInput transactionInput = new TransactionInput(params, depositTx, scriptProgram, outPoint, connectedOutputForInput.getValue()); + takerInputs.add(transactionInput); + depositTx.addInput(transactionInput); + } + + // Add all outputs from takersDepositTx to depositTx + for (TransactionOutput output : takersDepositTx.getOutputs()) { + depositTx.addOutput(output); + } + + // Sign inputs + for (int i = 0; i < offererConnectedOutputsForAllInputs.size(); i++) { + TransactionInput input = depositTx.getInput(i); + signInput(depositTx, input, i); + checkScriptSig(depositTx, input, i); + } + + // subtract change amount + for (int i = 1; i < offererOutputs.size() + 1; i++) { + offererSpendingAmount = offererSpendingAmount.subtract(depositTx.getOutput(i).getValue()); + } + + if (offererInputAmount.compareTo(offererSpendingAmount) != 0) + throw new TransactionVerificationException("Offerers input amount does not match required value."); + + verifyTransaction(depositTx); + checkWalletConsistency(); + + // Broadcast depositTx + printTxWithInputs("depositTx", depositTx); + ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(depositTx); + Futures.addCallback(broadcastComplete, callback); + } + + public Transaction takerCommitsDepositTx(Transaction depositTx) throws WalletException { + // We need to recreate the tx we get a null pointer otherwise + depositTx = new Transaction(params, depositTx.bitcoinSerialize()); + + try { + wallet.receivePending(depositTx, null, true); + } catch (Throwable t) { + log.error(t.getMessage()); + t.printStackTrace(); + throw new WalletException(t); + } + + return depositTx; + } + + public TransactionDataResult offererCreatesAndSignsPayoutTx(Transaction depositTx, + Coin offererPayoutAmount, + Coin takerPayoutAmount, + String takerAddressString, + AddressEntry addressEntry) + throws AddressFormatException, TransactionVerificationException, WalletException { + + Transaction payoutTx = createPayoutTx(depositTx, offererPayoutAmount, takerPayoutAmount, addressEntry.getAddressString(), takerAddressString); + + TransactionInput input = payoutTx.getInput(0); + TransactionOutput multiSigOutput = input.getConnectedOutput(); + Script multiSigScript = multiSigOutput.getScriptPubKey(); + Sha256Hash sigHash = payoutTx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature offererSignature = addressEntry.getKeyPair().sign(sigHash); + + verifyTransaction(payoutTx); + + return new TransactionDataResult(payoutTx, offererSignature); + } + + public void takerSignsAndPublishPayoutTx(Transaction depositTx, + ECKey.ECDSASignature offererSignature, + Coin offererPayoutAmount, + Coin takerPayoutAmount, + String offererAddressString, + AddressEntry addressEntry, + FutureCallback callback) + throws AddressFormatException, TransactionVerificationException, WalletException { + + Transaction payoutTx = createPayoutTx(depositTx, offererPayoutAmount, takerPayoutAmount, offererAddressString, addressEntry.getAddressString()); + + TransactionInput input = payoutTx.getInput(0); + TransactionOutput multiSigOutput = input.getConnectedOutput(); + Script multiSigScript = multiSigOutput.getScriptPubKey(); + Sha256Hash sigHash = payoutTx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature takerSignature = addressEntry.getKeyPair().sign(sigHash); + TransactionSignature takerTxSig = new TransactionSignature(takerSignature, Transaction.SigHash.ALL, false); + TransactionSignature offererTxSig = new TransactionSignature(offererSignature, Transaction.SigHash.ALL, false); + Script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(offererTxSig, takerTxSig)); + input.setScriptSig(inputScript); + + verifyTransaction(payoutTx); + checkWalletConsistency(); + checkScriptSig(payoutTx, input, 0); + input.verify(multiSigOutput); + + printTxWithInputs("payoutTx", payoutTx); + ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(payoutTx); + Futures.addCallback(broadcastComplete, callback); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Private methods + /////////////////////////////////////////////////////////////////////////////////////////// + + private Script getMultiSigOutputScript(byte[] offererPubKey, byte[] takerPubKey, byte[] arbitratorPubKey) { + ECKey offererKey = ECKey.fromPublicOnly(offererPubKey); + ECKey takerKey = ECKey.fromPublicOnly(takerPubKey); + ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey); + + List keys = ImmutableList.of(offererKey, takerKey, arbitratorKey); + return ScriptBuilder.createMultiSigOutputScript(2, keys); + } + + private Transaction createPayoutTx(Transaction depositTx, Coin offererPayoutAmount, Coin takerPayoutAmount, + String offererAddressString, String takerAddressString) throws AddressFormatException { + + TransactionOutput multiSigOutput = depositTx.getOutput(0); + Transaction tx = new Transaction(params); + tx.addInput(multiSigOutput); + tx.addOutput(offererPayoutAmount, new Address(params, offererAddressString)); + tx.addOutput(takerPayoutAmount, new Address(params, takerAddressString)); + return tx; + } + + public static void printTxWithInputs(String tracePrefix, Transaction tx) { + log.trace(tracePrefix + ": " + tx.toString()); + for (TransactionInput input : tx.getInputs()) { + if (input.getConnectedOutput() != null) + log.trace(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString()); + else + log.trace(tracePrefix + ": Transaction already has inputs but we don't have the connected outputs, so we don't know the value."); + } + } + + private void checkWalletConsistency() throws WalletException { + try { + log.trace("Check if wallet is consistent before commit."); + checkState(wallet.isConsistent()); + } catch (Throwable t) { + t.printStackTrace(); + log.error(t.getMessage()); + throw new WalletException(t); + } + } + + private void verifyTransaction(Transaction transaction) throws TransactionVerificationException { + try { + log.trace("Verify transaction"); + transaction.verify(); + } catch (Throwable t) { + t.printStackTrace(); + log.error(t.getMessage()); + throw new TransactionVerificationException(t); + } + } + + private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException, TransactionVerificationException { + Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); + ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); + Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false); + ECKey.ECDSASignature signature = sigKey.sign(hash); + TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false); + if (scriptPubKey.isSentToRawPubKey()) { + input.setScriptSig(ScriptBuilder.createInputScript(txSig)); + } + else if (scriptPubKey.isSentToAddress()) { + input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); + } + else { + throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); + } + } + + private void checkScriptSig(Transaction transaction, TransactionInput input, int inputIndex) throws TransactionVerificationException { + try { + log.trace("Verifies that this script (interpreted as a scriptSig) correctly spends the given scriptPubKey. Check input at index: " + inputIndex); + input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey()); + } catch (Throwable t) { + t.printStackTrace(); + log.error(t.getMessage()); + throw new TransactionVerificationException(t); + } + } + + /*private void checkScriptSigForAllInputs(Transaction transaction) throws TransactionVerificationException { + int inputIndex = 0; + for (TransactionInput input : transaction.getInputs()) { + checkScriptSig(transaction, input, inputIndex); + inputIndex++; + } + }*/ + + private void removeSignatures(Transaction transaction) throws InsufficientMoneyException { + for (TransactionInput input : transaction.getInputs()) { + input.setScriptSig(new Script(new byte[]{})); + } + } + + private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws WalletException { + try { + // Lets let the framework do the work to find the right inputs + Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); + sendRequest.shuffleOutputs = false; + // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) + sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); + sendRequest.changeAddress = addressEntry.getAddress(); + // With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. + // We don't commit that tx to the wallet as it will be changed later and it's not signed yet. + // So it will not change the wallet balance. + wallet.completeTx(sendRequest); + } catch (Throwable t) { + throw new WalletException(t); + } + } + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Inner classes + /////////////////////////////////////////////////////////////////////////////////////////// + + public class TransactionDataResult { + private List connectedOutputsForAllInputs; + private List outputs; + private Transaction depositTx; + + + private Transaction payoutTx; + private ECKey.ECDSASignature offererSignature; + + public TransactionDataResult(List connectedOutputsForAllInputs, List outputs) { + this.connectedOutputsForAllInputs = connectedOutputsForAllInputs; + this.outputs = outputs; + } + + public TransactionDataResult(Transaction depositTx, List connectedOutputsForAllInputs, List outputs) { + this.depositTx = depositTx; + this.connectedOutputsForAllInputs = connectedOutputsForAllInputs; + this.outputs = outputs; + } + + public TransactionDataResult(Transaction payoutTx, ECKey.ECDSASignature offererSignature) { + + this.payoutTx = payoutTx; + this.offererSignature = offererSignature; + } + + public List getOutputs() { + return outputs; + } + + public List getConnectedOutputsForAllInputs() { + return connectedOutputsForAllInputs; + } + + public Transaction getDepositTx() { + return depositTx; + } + + public Transaction getPayoutTx() { + return payoutTx; + } + + public ECKey.ECDSASignature getOffererSignature() { + return offererSignature; + } + } +} diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 456b9dd2d1..ca35b7477c 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -17,9 +17,6 @@ package io.bitsquare.btc; -import io.bitsquare.btc.exceptions.SigningException; -import io.bitsquare.btc.exceptions.TransactionVerificationException; -import io.bitsquare.btc.exceptions.WalletException; import io.bitsquare.btc.listeners.AddressConfidenceListener; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.TxConfidenceListener; @@ -34,28 +31,23 @@ import org.bitcoinj.core.DownloadListener; import org.bitcoinj.core.ECKey; import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.NetworkParameters; -import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionInput; -import org.bitcoinj.core.TransactionOutPoint; import org.bitcoinj.core.TransactionOutput; import org.bitcoinj.core.Wallet; import org.bitcoinj.core.WalletEventListener; import org.bitcoinj.crypto.DeterministicKey; -import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.kits.WalletAppKit; import org.bitcoinj.params.MainNetParams; import org.bitcoinj.params.RegTestParams; import org.bitcoinj.params.TestNet3Params; -import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.utils.Threading; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Service; import java.io.File; @@ -87,7 +79,6 @@ import rx.Observable; import rx.subjects.BehaviorSubject; import rx.subjects.Subject; -import static com.google.inject.internal.util.$Preconditions.*; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; public class WalletService { @@ -120,6 +111,8 @@ public class WalletService { private AddressEntry arbitratorDepositAddressEntry; private @GuardedBy(LOCK_NAME) List addressEntryList = new ArrayList<>(); + private TradeService tradeService; + /////////////////////////////////////////////////////////////////////////////////////////// // Constructor /////////////////////////////////////////////////////////////////////////////////////////// @@ -162,6 +155,9 @@ public class WalletService { walletAppKit.peerGroup().setMaxConnections(11); walletAppKit.peerGroup().setBloomFilterFalsePositiveRate(0.00001); initWallet(); + + tradeService = new TradeService(params, wallet, walletAppKit, feePolicy); + status.onCompleted(); } }; @@ -479,6 +475,10 @@ public class WalletService { // Transactions /////////////////////////////////////////////////////////////////////////////////////////// + public TradeService getTradeService() { + return tradeService; + } + public void payRegistrationFee(String stringifiedBankAccounts, FutureCallback callback) throws InsufficientMoneyException { log.debug("payRegistrationFee"); @@ -513,51 +513,7 @@ public class WalletService { printTxWithInputs("payRegistrationFee", tx); } - public Transaction createOfferFeeTx(String offerId) throws InsufficientMoneyException { - log.trace("createOfferFeeTx"); - Transaction tx = new Transaction(params); - Coin fee = FeePolicy.CREATE_OFFER_FEE.subtract(FeePolicy.TX_FEE); - log.trace("fee: " + fee.toFriendlyString()); - tx.addOutput(fee, feePolicy.getAddressForCreateOfferFee()); - Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); - sendRequest.shuffleOutputs = false; - // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to - // wait for 1 confirmation) - AddressEntry addressEntry = getAddressEntry(offerId); - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); - sendRequest.changeAddress = addressEntry.getAddress(); - wallet.completeTx(sendRequest); - printTxWithInputs("payCreateOfferFee", tx); - return tx; - } - - public void broadcastCreateOfferFeeTx(Transaction tx, FutureCallback callback) { - log.trace("broadcast tx"); - ListenableFuture future = walletAppKit.peerGroup().broadcastTransaction(tx); - Futures.addCallback(future, callback); - } - - public String payTakeOfferFee(String offerId, FutureCallback callback) throws InsufficientMoneyException { - Transaction tx = new Transaction(params); - Coin fee = FeePolicy.TAKE_OFFER_FEE.subtract(FeePolicy.TX_FEE); - log.trace("fee: " + fee.toFriendlyString()); - tx.addOutput(fee, feePolicy.getAddressForTakeOfferFee()); - - Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); - sendRequest.shuffleOutputs = false; - // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to - // wait for 1 confirmation) - sendRequest.coinSelector = new AddressBasedCoinSelector(params, getAddressEntry(offerId), true); - sendRequest.changeAddress = getAddressEntry(offerId).getAddress(); - Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); - Futures.addCallback(sendResult.broadcastComplete, callback); - - printTxWithInputs("payTakeOfferFee", tx); - log.debug("tx=" + tx); - - return tx.getHashAsString(); - } - + /////////////////////////////////////////////////////////////////////////////////////////// // Withdrawal @@ -595,6 +551,8 @@ public class WalletService { // Trade process /////////////////////////////////////////////////////////////////////////////////////////// + + /* public TransactionDataResult offererCreatesDepositTxInputs(Coin inputAmount, AddressEntry addressInfo) throws InsufficientMoneyException, TransactionVerificationException, WalletException { @@ -625,13 +583,13 @@ public class WalletService { checkWalletConsistency(); // The created tx looks like: - /* + *//* IN[0] any input > inputAmount (including tx fee) (unsigned) IN[1...n] optional inputs supported, but currently there is just 1 input (unsigned) OUT[0] dummyOutputAmount (inputAmount - tx fee) OUT[1] Optional Change = inputAmount - dummyOutputAmount - tx fee OUT[2...n] optional more outputs are supported, but currently there is just max. 1 optional change output - */ + *//* printTxWithInputs("dummyTX", dummyTX); @@ -649,250 +607,9 @@ public class WalletService { } return new TransactionDataResult(connectedOutputsForAllInputs, outputs); - } + }*/ - public TransactionDataResult takerCreatesAndSignsDepositTx(Coin takerInputAmount, - Coin msOutputAmount, - List offererConnectedOutputsForAllInputs, - List offererOutputs, - AddressEntry addressInfo, - byte[] offererPubKey, - byte[] takerPubKey, - byte[] arbitratorPubKey) throws InsufficientMoneyException, SigningException, - TransactionVerificationException, WalletException { - - checkArgument(offererConnectedOutputsForAllInputs.size() > 0); - - // To keep tx consistent with tx used for publishing we use always following ordering of inputs and outputs (offerer first then taker): - /* - IN[0] offerer (mandatory) e.g. 0.1 BTC - IN[...] optional additional offerer inputs (normally never used as we pay from trade fee tx and always have 1 output there) - IN[...] taker (mandatory) e.g. 1.1001 BTC - IN[...] optional additional taker inputs (normally never used as we pay from trade fee tx and always have 1 output there) - OUT[0] Multisig output (include tx fee for payout tx) e.g. 1.2001 - OUT[1] offerer change (normally never used as we pay from trade fee tx and always have 1 output there) - OUT[...] optional additional offerer outputs (supported but no use case yet for that) - OUT[...] taker change (normally never used as we pay from trade fee tx and always have 1 output there) - OUT[...] optional additional taker outputs (supported but no use case yet for that) - FEE tx fee 0.0001 BTC - */ - - // First we construct a dummy TX to get the inputs and outputs we want to use for the real deposit tx. Same as in first step at offerer. - Transaction dummyTx = new Transaction(params); - Coin dummyOutputAmount = takerInputAmount.subtract(FeePolicy.TX_FEE); - TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params)); - dummyTx.addOutput(dummyOutput); - addAvailableInputsAndChangeOutputs(dummyTx, addressInfo); - List takerInputs = dummyTx.getInputs(); - List takerOutputs = new ArrayList<>(); - // we store optional change outputs (ignoring dummyOutput) - for (int i = 1; i < dummyTx.getOutputs().size(); i++) { - takerOutputs.add(dummyTx.getOutput(i)); - } - - // Now we construct real deposit tx - Transaction depositTx = new Transaction(params); - - // Add offerer inputs (normally its just 1 input) - for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { - TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); - TransactionInput transactionInput = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); - depositTx.addInput(transactionInput); - } - - // Add taker inputs - List connectedOutputsForAllTakerInputs = new ArrayList<>(); - for (TransactionInput input : takerInputs) { - depositTx.addInput(input); - connectedOutputsForAllTakerInputs.add(input.getConnectedOutput()); - } - - // Add MultiSig output - Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); - // Tx fee for deposit tx will be paid by offerer. - TransactionOutput msOutput = new TransactionOutput(params, depositTx, msOutputAmount, multiSigOutputScript.getProgram()); - depositTx.addOutput(msOutput); - - // Add optional offerer outputs - for (TransactionOutput output : offererOutputs) { - depositTx.addOutput(output); - } - - Coin takersSpendingAmount = Coin.ZERO; - - // Add optional taker outputs - for (TransactionOutput output : takerOutputs) { - depositTx.addOutput(output); - - // subtract change amount - takersSpendingAmount = takersSpendingAmount.subtract(output.getValue()); - } - - // Sign inputs - for (int i = offererConnectedOutputsForAllInputs.size(); i < depositTx.getInputs().size(); i++) { - TransactionInput input = depositTx.getInput(i); - signInput(depositTx, input, i); - checkScriptSig(depositTx, input, i); - - // add up spending amount - takersSpendingAmount = takersSpendingAmount.add(input.getConnectedOutput().getValue()); - } - - if (takerInputAmount.compareTo(takersSpendingAmount) != 0) - throw new TransactionVerificationException("Takers input amount does not match required value."); - - verifyTransaction(depositTx); - checkWalletConsistency(); - - printTxWithInputs("depositTx", depositTx); - - return new TransactionDataResult(depositTx, connectedOutputsForAllTakerInputs, takerOutputs); - } - - public void offererSignsAndPublishTx(Transaction takersDepositTx, - List offererConnectedOutputsForAllInputs, - List takerConnectedOutputsForAllInputs, - List offererOutputs, - Coin offererInputAmount, - byte[] offererPubKey, - byte[] takerPubKey, - byte[] arbitratorPubKey, - FutureCallback callback) throws SigningException, TransactionVerificationException, WalletException { - - checkArgument(offererConnectedOutputsForAllInputs.size() > 0); - checkArgument(takerConnectedOutputsForAllInputs.size() > 0); - - // Check if takers Multisig script is identical to mine - Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); - if (!takersDepositTx.getOutput(0).getScriptPubKey().equals(multiSigOutputScript)) - throw new TransactionVerificationException("Takers multiSigOutputScript does not match to my multiSigOutputScript"); - - // The outpoints are not available from the serialized takersDepositTx, so we cannot use that tx directly, but we use it to construct a new depositTx - Transaction depositTx = new Transaction(params); - - // Add offerer inputs - Coin offererSpendingAmount = Coin.ZERO; - for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { - TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); - TransactionInput input = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); - depositTx.addInput(input); - - // add up spending amount - offererSpendingAmount = offererSpendingAmount.add(input.getConnectedOutput().getValue()); - } - - // Add taker inputs and apply signature - List takerInputs = new ArrayList<>(); - for (TransactionOutput connectedOutputForInput : takerConnectedOutputsForAllInputs) { - TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); - - // We grab the signature from the takersDepositTx and apply it to the new tx input - TransactionInput takerInput = takersDepositTx.getInputs().get(offererConnectedOutputsForAllInputs.size()); - byte[] scriptProgram = takerInput.getScriptSig().getProgram(); - if (scriptProgram.length == 0) - throw new TransactionVerificationException("Inputs from taker not singed."); - - TransactionInput transactionInput = new TransactionInput(params, depositTx, scriptProgram, outPoint, connectedOutputForInput.getValue()); - takerInputs.add(transactionInput); - depositTx.addInput(transactionInput); - } - - // Add all outputs from takersDepositTx to depositTx - for (TransactionOutput output : takersDepositTx.getOutputs()) { - depositTx.addOutput(output); - } - - // Sign inputs - for (int i = 0; i < offererConnectedOutputsForAllInputs.size(); i++) { - TransactionInput input = depositTx.getInput(i); - signInput(depositTx, input, i); - checkScriptSig(depositTx, input, i); - } - - // subtract change amount - for (int i = 1; i < offererOutputs.size() + 1; i++) { - offererSpendingAmount = offererSpendingAmount.subtract(depositTx.getOutput(i).getValue()); - } - - if (offererInputAmount.compareTo(offererSpendingAmount) != 0) - throw new TransactionVerificationException("Offerers input amount does not match required value."); - - verifyTransaction(depositTx); - checkWalletConsistency(); - - // Broadcast depositTx - printTxWithInputs("depositTx", depositTx); - ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(depositTx); - Futures.addCallback(broadcastComplete, callback); - } - - public Transaction takerCommitsDepositTx(Transaction depositTx) throws WalletException { - // We need to recreate the tx we get a null pointer otherwise - depositTx = new Transaction(params, depositTx.bitcoinSerialize()); - - try { - wallet.receivePending(depositTx, null, true); - } catch (Throwable t) { - log.error(t.getMessage()); - t.printStackTrace(); - throw new WalletException(t); - } - - return depositTx; - } - - public TransactionDataResult offererCreatesAndSignsPayoutTx(Transaction depositTx, - Coin offererPayoutAmount, - Coin takerPayoutAmount, - String takerAddressString, - AddressEntry addressEntry) throws AddressFormatException, TransactionVerificationException { - // We create the payout tx - Transaction payoutTx = createPayoutTx(depositTx, offererPayoutAmount, takerPayoutAmount, addressEntry.getAddressString(), takerAddressString); - - // We create the signature for that tx - TransactionOutput multiSigOutput = payoutTx.getInput(0).getConnectedOutput(); - Script multiSigScript = multiSigOutput.getScriptPubKey(); - Sha256Hash sigHash = payoutTx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); - ECKey.ECDSASignature offererSignature = addressEntry.getKeyPair().sign(sigHash); - - verifyTransaction(payoutTx); - - return new TransactionDataResult(payoutTx, offererSignature); - } - - public void takerSignsAndPublishPayoutTx(Transaction depositTx, - ECKey.ECDSASignature offererSignature, - Coin offererPayoutAmount, - Coin takerPayoutAmount, - String offererAddressString, - AddressEntry addressEntry, - FutureCallback callback) throws AddressFormatException, TransactionVerificationException, - WalletException { - - // We create the payout tx - Transaction payoutTx = createPayoutTx(depositTx, offererPayoutAmount, takerPayoutAmount, offererAddressString, addressEntry.getAddressString()); - - // We sign that tx with our key and apply the signature from the offerer - TransactionInput input = payoutTx.getInput(0); - TransactionOutput multiSigOutput = input.getConnectedOutput(); - Script multiSigScript = multiSigOutput.getScriptPubKey(); - Sha256Hash sigHash = payoutTx.hashForSignature(0, multiSigScript, Transaction.SigHash.ALL, false); - ECKey.ECDSASignature takerSignature = addressEntry.getKeyPair().sign(sigHash); - TransactionSignature takerTxSig = new TransactionSignature(takerSignature, Transaction.SigHash.ALL, false); - TransactionSignature offererTxSig = new TransactionSignature(offererSignature, Transaction.SigHash.ALL, false); - Script inputScript = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(offererTxSig, takerTxSig)); - input.setScriptSig(inputScript); - - verifyTransaction(payoutTx); - checkWalletConsistency(); - checkScriptSig(payoutTx, input, 0); - input.verify(multiSigOutput); - - ListenableFuture broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(payoutTx); - Futures.addCallback(broadcastComplete, callback); - - printTxWithInputs("payoutTx", payoutTx); - } + /////////////////////////////////////////////////////////////////////////////////////////// @@ -900,7 +617,6 @@ public class WalletService { /////////////////////////////////////////////////////////////////////////////////////////// private void saveAddressInfoList() { - // use wallet extension? lock.lock(); try { persistence.write(this, "addressEntryList", addressEntryList); @@ -909,26 +625,6 @@ public class WalletService { } } - private Script getMultiSigOutputScript(byte[] offererPubKey, byte[] takerPubKey, byte[] arbitratorPubKey) { - ECKey offererKey = ECKey.fromPublicOnly(offererPubKey); - ECKey takerKey = ECKey.fromPublicOnly(takerPubKey); - ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey); - - List keys = ImmutableList.of(offererKey, takerKey, arbitratorKey); - return ScriptBuilder.createMultiSigOutputScript(2, keys); - } - - private Transaction createPayoutTx(Transaction depositTx, Coin offererPayoutAmount, Coin takerPayoutAmount, - String offererAddressString, String takerAddressString) throws AddressFormatException { - - TransactionOutput multiSigOutput = depositTx.getOutput(0); - Transaction tx = new Transaction(params); - tx.addInput(multiSigOutput); - tx.addOutput(offererPayoutAmount, new Address(params, offererAddressString)); - tx.addOutput(takerPayoutAmount, new Address(params, takerAddressString)); - return tx; - } - public static void printTxWithInputs(String tracePrefix, Transaction tx) { log.trace(tracePrefix + ": " + tx.toString()); for (TransactionInput input : tx.getInputs()) { @@ -939,87 +635,6 @@ public class WalletService { } } - private void checkWalletConsistency() throws WalletException { - try { - log.trace("Check if wallet is consistent before commit."); - checkState(wallet.isConsistent()); - } catch (Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - throw new WalletException(t); - } - } - - private void verifyTransaction(Transaction transaction) throws TransactionVerificationException { - try { - log.trace("Verify transaction"); - transaction.verify(); - } catch (Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - throw new TransactionVerificationException(t); - } - } - - private void signInput(Transaction transaction, TransactionInput input, int inputIndex) throws SigningException, TransactionVerificationException { - Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); - ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); - Sha256Hash hash = transaction.hashForSignature(inputIndex, scriptPubKey, Transaction.SigHash.ALL, false); - ECKey.ECDSASignature signature = sigKey.sign(hash); - TransactionSignature txSig = new TransactionSignature(signature, Transaction.SigHash.ALL, false); - if (scriptPubKey.isSentToRawPubKey()) { - input.setScriptSig(ScriptBuilder.createInputScript(txSig)); - } - else if (scriptPubKey.isSentToAddress()) { - input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); - } - else { - throw new SigningException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); - } - } - - private void checkScriptSig(Transaction transaction, TransactionInput input, int inputIndex) throws TransactionVerificationException { - try { - log.trace("Verifies that this script (interpreted as a scriptSig) correctly spends the given scriptPubKey. Check input at index: " + inputIndex); - input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey()); - } catch (Throwable t) { - t.printStackTrace(); - log.error(t.getMessage()); - throw new TransactionVerificationException(t); - } - } - - /*private void checkScriptSigForAllInputs(Transaction transaction) throws TransactionVerificationException { - int inputIndex = 0; - for (TransactionInput input : transaction.getInputs()) { - checkScriptSig(transaction, input, inputIndex); - inputIndex++; - } - }*/ - - private void removeSignatures(Transaction transaction) throws InsufficientMoneyException { - for (TransactionInput input : transaction.getInputs()) { - input.setScriptSig(new Script(new byte[]{})); - } - } - - private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws WalletException { - try { - // Lets let the framework do the work to find the right inputs - Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); - sendRequest.shuffleOutputs = false; - // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); - sendRequest.changeAddress = addressEntry.getAddress(); - // With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. - // We don't commit that tx to the wallet as it will be changed later and it's not signed yet. - // So it will not change the wallet balance. - wallet.completeTx(sendRequest); - } catch (Throwable t) { - throw new WalletException(t); - } - } - /////////////////////////////////////////////////////////////////////////////////////////// // Inner classes /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java index 0ef33790f7..f1a47a6a7c 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java @@ -52,7 +52,7 @@ public class BroadcastCreateOfferFeeTx extends Task { Coin balance = model.getWalletService().getBalanceForAddress(addressEntry.getAddress()); if (balance.compareTo(totalsNeeded) >= 0) { - model.getWalletService().broadcastCreateOfferFeeTx(model.getTransaction(), new FutureCallback() { + model.getWalletService().getTradeService().broadcastCreateOfferFeeTx(model.getTransaction(), new FutureCallback() { @Override public void onSuccess(Transaction transaction) { log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index a3d9fe862d..8b1d04f85a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -36,7 +36,8 @@ public class CreateOfferFeeTx extends Task { @Override protected void doRun() { try { - Transaction transaction = model.getWalletService().createOfferFeeTx(model.getOffer().getId()); + Transaction transaction = model.getWalletService().getTradeService().createOfferFeeTx( + model.getWalletService().getAddressEntry(model.getOffer().getId())); // We assume there will be no tx malleability. We add a check later in case the published offer has a different hash. model.getOffer().setOfferFeePaymentTxID(transaction.getHashAsString()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/OfferSharedModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/OfferSharedModel.java index ee2ff5de49..d3b62e3055 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/OfferSharedModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/OfferSharedModel.java @@ -20,6 +20,7 @@ package io.bitsquare.trade.protocol.trade; import io.bitsquare.bank.BankAccount; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.BlockChainService; +import io.bitsquare.btc.TradeService; import io.bitsquare.btc.WalletService; import io.bitsquare.crypto.SignatureService; import io.bitsquare.offer.Offer; @@ -54,10 +55,12 @@ public class OfferSharedModel extends SharedModel { protected final DeterministicKey registrationKeyPair; protected final byte[] arbitratorPubKey; protected final AddressEntry addressEntry; + private final TradeService tradeService; // data written/read by tasks protected TradeMessage tradeMessage; + public OfferSharedModel(Offer offer, TradeMessageService tradeMessageService, WalletService walletService, @@ -71,6 +74,7 @@ public class OfferSharedModel extends SharedModel { this.signatureService = signatureService; id = offer.getId(); + tradeService = walletService.getTradeService(); //TODO use default arbitrator for now arbitratorPubKey = offer.getArbitrators().get(0).getPubKey(); registrationPubKey = walletService.getRegistrationAddressEntry().getPubKey(); @@ -87,6 +91,10 @@ public class OfferSharedModel extends SharedModel { return id; } + public TradeService getTradeService() { + return tradeService; + } + public TradeMessage getTradeMessage() { return tradeMessage; } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/GetOffererDepositTxInputs.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/GetOffererDepositTxInputs.java index 11ef2df70f..931ec00f82 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/GetOffererDepositTxInputs.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/GetOffererDepositTxInputs.java @@ -19,7 +19,7 @@ package io.bitsquare.trade.protocol.trade.offerer.tasks; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.TradeService; import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel; import io.bitsquare.util.taskrunner.Task; import io.bitsquare.util.taskrunner.TaskRunner; @@ -41,7 +41,7 @@ public class GetOffererDepositTxInputs extends Task { try { Coin offererInputAmount = model.getTrade().getSecurityDeposit().add(FeePolicy.TX_FEE); AddressEntry addressInfo = model.getWalletService().getAddressEntry(model.getId()); - WalletService.TransactionDataResult result = model.getWalletService().offererCreatesDepositTxInputs(offererInputAmount, addressInfo); + TradeService.TransactionDataResult result = model.getTradeService().offererCreatesDepositTxInputs(offererInputAmount, addressInfo); model.setOffererConnectedOutputsForAllInputs(result.getConnectedOutputsForAllInputs()); model.setOffererOutputs(result.getOutputs()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SendBankTransferStartedMessage.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SendBankTransferStartedMessage.java index f058e6dd70..086bebbe3a 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SendBankTransferStartedMessage.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SendBankTransferStartedMessage.java @@ -41,7 +41,7 @@ public class SendBankTransferStartedMessage extends Task { model.getOffererSignature().encodeToDER(), model.getOffererPayoutAmount(), model.getTakerPayoutAmount(), - model.getWalletService().getAddressEntry(model.getId()).getAddressString()); + model.getAddressEntry().getAddressString()); model.getTradeMessageService().sendMessage(model.getTaker(), tradeMessage, new SendMessageListener() { @Override public void handleResult() { diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignAndPublishDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignAndPublishDepositTx.java index bccc0351fe..552a472495 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignAndPublishDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignAndPublishDepositTx.java @@ -44,7 +44,7 @@ public class SignAndPublishDepositTx extends Task { protected void doRun() { try { Coin offererInputAmount = model.getTrade().getSecurityDeposit().add(FeePolicy.TX_FEE); - model.getWalletService().offererSignsAndPublishTx( + model.getTradeService().offererSignsAndPublishTx( model.getTakerDepositTx(), model.getOffererConnectedOutputsForAllInputs(), model.getTakerConnectedOutputsForAllInputs(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignPayoutTx.java index f94d3a50a9..e4ba45e054 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/offerer/tasks/SignPayoutTx.java @@ -17,7 +17,7 @@ package io.bitsquare.trade.protocol.trade.offerer.tasks; -import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.TradeService; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel; import io.bitsquare.util.taskrunner.Task; @@ -43,7 +43,7 @@ public class SignPayoutTx extends Task { Coin offererPayoutAmount = trade.getTradeAmount().add(securityDeposit); Coin takerPayoutAmount = securityDeposit; - WalletService.TransactionDataResult result = model.getWalletService().offererCreatesAndSignsPayoutTx( + TradeService.TransactionDataResult result = model.getTradeService().offererCreatesAndSignsPayoutTx( trade.getDepositTx(), offererPayoutAmount, takerPayoutAmount, diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/PayTakeOfferFee.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/PayTakeOfferFee.java index 4ba66ea264..5154e5b87f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/PayTakeOfferFee.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/PayTakeOfferFee.java @@ -42,13 +42,13 @@ public class PayTakeOfferFee extends Task { @Override protected void doRun() { try { - model.getWalletService().payTakeOfferFee(model.getId(), new FutureCallback() { + model.getTradeService().payTakeOfferFee(model.getAddressEntry(), new FutureCallback() { @Override public void onSuccess(Transaction transaction) { log.debug("Take offer fee paid successfully. Transaction ID = " + transaction.getHashAsString()); model.getTrade().setTakeOfferFeeTxID(transaction.getHashAsString()); model.getTrade().setState(Trade.State.TAKE_OFFER_FEE_PAID); - + complete(); } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SendSignedTakerDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SendSignedTakerDepositTx.java index 87624632ee..5e14d5ca1f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SendSignedTakerDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SendSignedTakerDepositTx.java @@ -42,7 +42,7 @@ public class SendSignedTakerDepositTx extends Task { model.getNetworkPubKey(), model.getTrade().getContractAsJson(), model.getTrade().getTakerContractSignature(), - model.getWalletService().getAddressEntry(model.getId()).getAddressString(), + model.getAddressEntry().getAddressString(), model.getTakerDepositTx(), model.getTakerConnectedOutputsForAllInputs(), model.getTakerOutputs() diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SignAndPublishPayoutTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SignAndPublishPayoutTx.java index d0a728e75b..fe2f9c23e5 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SignAndPublishPayoutTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/SignAndPublishPayoutTx.java @@ -42,7 +42,7 @@ public class SignAndPublishPayoutTx extends Task { @Override protected void doRun() { try { - model.getWalletService().takerSignsAndPublishPayoutTx( + model.getTradeService().takerSignsAndPublishPayoutTx( model.getPublishedDepositTx(), model.getOffererSignature(), model.getOffererPayoutAmount(), diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCommitDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCommitDepositTx.java index fd07b84a7b..c59ee89c7d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCommitDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCommitDepositTx.java @@ -37,7 +37,7 @@ public class TakerCommitDepositTx extends Task { @Override protected void doRun() { try { - Transaction transaction = model.getWalletService().takerCommitsDepositTx(model.getPublishedDepositTx()); + Transaction transaction = model.getTradeService().takerCommitsDepositTx(model.getPublishedDepositTx()); model.getTrade().setDepositTx(transaction); model.getTrade().setState(Trade.State.DEPOSIT_PUBLISHED); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCreatesAndSignsDepositTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCreatesAndSignsDepositTx.java index f9c54ad516..69144cf9e9 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCreatesAndSignsDepositTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/taker/tasks/TakerCreatesAndSignsDepositTx.java @@ -18,7 +18,7 @@ package io.bitsquare.trade.protocol.trade.taker.tasks; import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.WalletService; +import io.bitsquare.btc.TradeService; import io.bitsquare.trade.Trade; import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerModel; import io.bitsquare.util.taskrunner.Task; @@ -42,7 +42,7 @@ public class TakerCreatesAndSignsDepositTx extends Task { Coin takerInputAmount = model.getTrade().getTradeAmount().add(model.getTrade().getSecurityDeposit()).add(FeePolicy.TX_FEE); Coin msOutputAmount = takerInputAmount.add(model.getTrade().getSecurityDeposit()); - WalletService.TransactionDataResult result = model.getWalletService().takerCreatesAndSignsDepositTx( + TradeService.TransactionDataResult result = model.getTradeService().takerCreatesAndSignsDepositTx( takerInputAmount, msOutputAmount, model.getOffererConnectedOutputsForAllInputs(),