Refactor trade transactions

This commit is contained in:
Manfred Karrer 2015-03-15 23:46:46 +01:00
parent 2377db62fc
commit 6e3f83b8e0
33 changed files with 685 additions and 489 deletions

View file

@ -17,6 +17,9 @@
package io.bitsquare.btc; 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.AddressConfidenceListener;
import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.listeners.TxConfidenceListener; import io.bitsquare.btc.listeners.TxConfidenceListener;
@ -31,7 +34,6 @@ import org.bitcoinj.core.DownloadListener;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException; import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters; import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Sha256Hash; import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionConfidence; import org.bitcoinj.core.TransactionConfidence;
@ -87,6 +89,7 @@ import rx.Observable;
import rx.subjects.BehaviorSubject; import rx.subjects.BehaviorSubject;
import rx.subjects.Subject; import rx.subjects.Subject;
import static com.google.inject.internal.util.$Preconditions.checkState;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
public class WalletService { public class WalletService {
@ -597,299 +600,219 @@ public class WalletService {
// Trade process // Trade process
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// 1. step: deposit tx // 1. step: Define offerers inputs and outputs for the deposit tx
// Offerer creates the temporary 2of3 multiSig deposit tx with his unsigned input and change output public TransactionDataResult offererCreatesDepositTxInputs(Coin inputAmount, AddressEntry addressInfo) throws InsufficientMoneyException,
public Transaction offererPreparesDepositTx(Coin offererInputAmount, TransactionVerificationException, WalletException {
String tradeId,
byte[] offererPubKey,
byte[] takerPubKey,
byte[] arbitratorPubKey) throws InsufficientMoneyException {
log.debug("offererCreatesMSTxAndAddPayment");
log.trace("inputs: ");
log.trace("offererInputAmount=" + offererInputAmount.toFriendlyString());
log.trace("offererPubKey=" + offererPubKey);
log.trace("takerPubKey=" + takerPubKey);
log.trace("arbitratorPubKey=" + arbitratorPubKey);
log.trace("tradeId=" + tradeId);
// We need to subtract the fee as it will go to the miners // We pay the tx fee 2 times to the deposit tx:
Coin offererInput = offererInputAmount.subtract(FeePolicy.TX_FEE); // 1. Will be spent when publishing the deposit tx (paid by offerer)
log.trace("amountToPay=" + offererInput.toFriendlyString()); // 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.
// We pay the offererInputAmount to a temporary MS output which will be changed later to the correct value. // inputAmount includes the tx fee. So we subtract the fee to get the dummyOutputAmount.
// With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. Coin dummyOutputAmount = inputAmount.subtract(FeePolicy.TX_FEE);
// Later with more customized coin selection we can use a custom CoinSelector implementation.
// 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.
// The btc tx fee will be included by the completeTx() call, so we don't need to add it manually.
Transaction tx = new Transaction(params);
Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey);
tx.addOutput(offererInput, multiSigOutputScript); // that output is just a dummy for input calculation
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx); Transaction dummyTX = new Transaction(params);
sendRequest.shuffleOutputs = false; // 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.
AddressEntry addressEntry = getAddressInfo(tradeId); // We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee.
// we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to TransactionOutput msOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, new ECKey());
// wait for 1 confirmation) dummyTX.addOutput(msOutput);
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true);
sendRequest.changeAddress = addressEntry.getAddress();
wallet.completeTx(sendRequest);
// The completeTx() call signs the input, but we don't want to pass over a signed tx so we remove the // Fin the needed inputs to pay the output, optional add change output.
// signature to make sure the tx is invalid for publishing // 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
// We have exactly 1 input as our spending transaction output is from the create offer fee payment and has only 1 output // offer fee payment. In future changes (in case of no offer fee) multiple inputs might become used.
tx.getInputs().get(0).setScriptSig(new Script(new byte[]{})); addAvailableInputsAndChangeOutputs(dummyTX, addressInfo);
log.trace("verify tx"); // The completeTx() call signs the input, but we don't want to pass over signed tx inputs
tx.verify(); // 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: // The created tx looks like:
/* /*
IN[0] any input > offererInputAmount + fee (unsigned) IN[0] any input > inputAmount (including tx fee) (unsigned)
OUT[0] MS offererInputAmount IN[1...n] optional inputs supported, but currently there is just 1 input (unsigned)
OUT[1] Optional Change = input - offererInputAmount - fee btc tx fee 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
*/ */
log.trace("Check if wallet is consistent: result=" + wallet.isConsistent()); printInputs("dummyTX", dummyTX);
printInputs("offererCreatesMSTxAndAddPayment", tx); log.debug("dummyTX created: " + dummyTX);
log.debug("tx = " + tx);
return tx; List<TransactionOutput> connectedOutputsForAllInputs = new ArrayList<>();
for (TransactionInput input : dummyTX.getInputs()) {
connectedOutputsForAllInputs.add(input.getConnectedOutput());
}
// Only save offerer outputs, the MS output is ignored
List<TransactionOutput> outputs = new ArrayList<>();
for (TransactionOutput output : dummyTX.getOutputs()) {
if (output.equals(msOutput))
continue;
outputs.add(output);
}
return new TransactionDataResult(connectedOutputsForAllInputs, outputs);
} }
// 2. step: deposit tx // 2. step: Taker creates a deposit tx and signs his inputs
// Taker adds his input and change output, changes the multiSig amount to the correct value and sign his input public TransactionDataResult takerCreatesAndSignsDepositTx(Coin inputAmount,
public Transaction takerAddPaymentAndSignTx(Coin takerInputAmount, Coin msOutputAmount,
Coin msOutputAmount, List<TransactionOutput> offererConnectedOutputsForAllInputs,
Transaction preparedDepositTx, List<TransactionOutput> offererOutputs,
String tradeId, AddressEntry addressInfo,
byte[] offererPubKey, byte[] offererPubKey,
byte[] takerPubKey, byte[] takerPubKey,
byte[] arbitratorPubKey) throws InsufficientMoneyException { byte[] arbitratorPubKey) throws InsufficientMoneyException, SigningException,
log.debug("takerAddPaymentAndSignTx"); TransactionVerificationException, WalletException {
log.trace("inputs: ");
log.trace("takerInputAmount=" + takerInputAmount.toFriendlyString());
log.trace("msOutputAmount=" + msOutputAmount.toFriendlyString());
log.trace("offererPubKey=" + offererPubKey);
log.trace("takerPubKey=" + takerPubKey);
log.trace("arbitratorPubKey=" + arbitratorPubKey);
log.trace("preparedDepositTransaction=" + preparedDepositTx);
log.trace("tradeId=" + tradeId);
// We pay the btc tx fee 2 times to the deposit tx: // TODO verify amounts, addresses, MS
// 1. will be spent to miners when publishing the deposit tx
// 2. will be as added to the MS amount, so when spending the payout tx the fee is already there and the Transaction depositTx = new Transaction(params);
// outputs are not changed by fee reduction Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey);
// Both traders pay 1 times a fee, so it is equally split between them // We use temporary inputAmount as the value for the output amount to get the correct inputs from the takers side.
// Later when we add the offerer inputs we replace the output amount with the real msOutputAmount
// Tx fee for deposit tx will be paid by offerer.
TransactionOutput msOutput = new TransactionOutput(params, depositTx, inputAmount, multiSigOutputScript.getProgram());
depositTx.addOutput(msOutput);
// We do exactly the same as in the 1. step but with the takers input. // Not lets find the inputs to satisfy that output and add an optional change output
Transaction tempTx = new Transaction(params); addAvailableInputsAndChangeOutputs(depositTx, addressInfo);
Script multiSigOutputScript = getMultiSigScript(offererPubKey, takerPubKey, arbitratorPubKey);
tempTx.addOutput(takerInputAmount, multiSigOutputScript);
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tempTx); // Now as we have the takers inputs and outputs we replace the temporary output amount with the real msOutputAmount
sendRequest.shuffleOutputs = false; msOutput.setValue(msOutputAmount);
AddressEntry addressEntry = getAddressInfo(tradeId);
// 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);
printInputs("tempTx", tempTx); // Save reference to inputs for signing, before we add offerer inputs
log.trace("tempTx=" + tempTx); List<TransactionInput> takerInputs = new ArrayList<>(depositTx.getInputs());
// That tx has signed input, but we don't need to remove it as we don't send that tx out, List<TransactionOutput> connectedOutputsForAllTakerInputs = new ArrayList<>();
// it is just used temporary. for (TransactionInput input : takerInputs) {
connectedOutputsForAllTakerInputs.add(input.getConnectedOutput());
// The created tempTx looks like:
/*
IN[0] any input taker > takerInputAmount + fee (signed)
OUT[0] MS takerInputAmount
OUT[1] Optional change = input taker - takerInputAmount - fee btc tx fee
*/
// Now we construct the real 2of3 multiSig tx from the serialized offerers tx
// The serialized offerers tx looks like:
/*
IN[0] any input offerer > offererInputAmount + fee (unsigned)
OUT[0] MS offererInputAmount
OUT[1] Change = input offerer - offererInputAmount - fee
btc tx fee
*/
// Now we add the inputs and outputs from our temp tx and change the multiSig amount to the correct value
for (TransactionInput input : tempTx.getInputs()) {
preparedDepositTx.addInput(input);
}
// handle optional change output
if (tempTx.getOutputs().size() == 2) {
preparedDepositTx.addOutput(tempTx.getOutput(1));
} }
preparedDepositTx.getOutput(0).setValue(msOutputAmount); // Lets save the takerOutputs for passing later to the result, the MS output is ignored
List<TransactionOutput> takerOutputs = new ArrayList<>();
// Now we sign our input (index 1) for (TransactionOutput output : depositTx.getOutputs()) {
TransactionInput input = preparedDepositTx.getInput(1); if (output.equals(msOutput))
if (input == null || input.getConnectedOutput() == null) continue;
log.error("Must not happen - input or input.getConnectedOutput() is null: " + input); takerOutputs.add(output);
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet);
Sha256Hash hash = preparedDepositTx.hashForSignature(1, scriptPubKey, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature ecSig = sigKey.sign(hash);
TransactionSignature txSig = new TransactionSignature(ecSig, 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 ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
} }
log.trace("check if it can be correctly spent for input 1"); // Add all inputs from offerer (normally its just 1 input)
input.getScriptSig().correctlySpends(preparedDepositTx, 1, scriptPubKey); for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) {
TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
TransactionInput transactionInput = new TransactionInput(params, depositTx, new byte[]{}, outPoint);
depositTx.addInput(transactionInput);
}
log.trace("verify tx"); // Add optional outputs
preparedDepositTx.verify(); for (TransactionOutput output : offererOutputs) {
depositTx.addOutput(output);
}
// The resulting tx looks like: printInputs("depositTx", depositTx);
/* log.debug("depositTx = " + depositTx);
IN[0] any input offerer > offererInputAmount + fee (unsigned) e.g.: 0.1001
IN[1] any input taker > takerInputAmount + fee (signed) e.g.: 1.1001
OUT[0] MS offererInputAmount e.g.: 1.2001
OUT[1] Change = input offerer - offererInputAmount - fee e.g.: 0 if input is matching correct value
OUT[2] Change = input taker - takerInputAmount - fee e.g.: 0 if input is matching correct value btc tx fee e.g.: 0.1001
*/
// We must not commit that tx to the wallet as we will get it over the network when the offerer // Sign taker inputs
// publishes it and it will have a different tx hash, so it would invalidate our wallet. // Taker inputs are the first inputs (0 -n), so the index of takerInputs and depositTx.getInputs() matches for the number of takerInputs.
int index = 0;
for (TransactionInput input : takerInputs) {
log.debug("signInput input "+input.toString());
log.debug("signInput index "+index);
signInput(depositTx, input, index);
checkScriptSig(depositTx, input, index);
index++;
}
log.trace("Check if wallet is consistent before commit: result=" + wallet.isConsistent()); verifyTransaction(depositTx);
printInputs("takerAddPaymentAndSignTx", preparedDepositTx); checkWalletConsistency();
log.debug("tx = " + preparedDepositTx);
return preparedDepositTx; printInputs("depositTx", depositTx);
log.debug("depositTx = " + depositTx);
return new TransactionDataResult(depositTx, connectedOutputsForAllTakerInputs, takerOutputs);
} }
// 3. step: deposit tx // 3. step: deposit tx
// Offerer signs tx and publishes it // Offerer signs tx and publishes it
public void offererSignAndPublishTx(Transaction preparedDepositTx, public void offererSignAndPublishTx(Transaction takersDepositTx,
Transaction takersSignedDepositTx, List<TransactionOutput> takersConnectedOutputsForAllInputs,
Transaction takersFromTx, List<TransactionOutput> offererConnectedOutputsForAllInputs,
byte[] takersSignedScriptSig, byte[] offererPubKey,
long offererTxOutIndex, byte[] takerPubKey,
long takerTxOutIndex, byte[] arbitratorPubKey,
FutureCallback<Transaction> callback) { FutureCallback<Transaction> callback) throws SigningException, TransactionVerificationException, WalletException {
log.debug("offererSignAndPublishTx");
log.trace("inputs: ");
log.trace("preparedDepositTx=" + preparedDepositTx);
log.trace("takersSignedTx=" + takersSignedDepositTx);
log.trace("takersFromTx=" + takersFromTx);
log.trace("takersSignedScriptSig=" + takersSignedScriptSig);
log.trace("callback=" + callback);
// We create an empty tx (did not find a way to manipulate a tx input, otherwise the takers tx could be used // TODO verify amounts, addresses, MS
// directly and add the offerers input and output)
Transaction tx = new Transaction(params); // 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);
printInputs("preparedDepositTx", preparedDepositTx); // We save offererInputs for later signing when tx is fully constructed
log.trace("preparedDepositTx = " + preparedDepositTx); List<TransactionInput> offererInputs = new ArrayList<>();
// add input // Add all inputs from offerer (normally its just 1 input)
Transaction offerersFirstTxConnOut = wallet.getTransaction(preparedDepositTx.getInput(0).getOutpoint().getHash()); for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) {
TransactionOutPoint offerersFirstTxOutPoint = new TransactionOutPoint(params, offererTxOutIndex, offerersFirstTxConnOut); TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
TransactionInput offerersFirstTxInput = new TransactionInput(params, tx, new byte[]{}, offerersFirstTxOutPoint); TransactionInput input = new TransactionInput(params, depositTx, new byte[]{}, outPoint);
offerersFirstTxInput.setParent(tx); offererInputs.add(input);
tx.addInput(offerersFirstTxInput); depositTx.addInput(input);
printInputs("takersSignedTxInput", takersSignedDepositTx);
log.trace("takersSignedTx = " + takersSignedDepositTx);
// add input
TransactionOutPoint takersSignedTxOutPoint = new TransactionOutPoint(params, takerTxOutIndex, takersFromTx);
TransactionInput takersSignedTxInput = new TransactionInput(
params, tx, takersSignedScriptSig, takersSignedTxOutPoint);
takersSignedTxInput.setParent(tx);
tx.addInput(takersSignedTxInput);
// add outputs from takers tx, they are already correct
tx.addOutput(takersSignedDepositTx.getOutput(0));
if (takersSignedDepositTx.getOutputs().size() > 1) {
tx.addOutput(takersSignedDepositTx.getOutput(1));
}
if (takersSignedDepositTx.getOutputs().size() == 3) {
tx.addOutput(takersSignedDepositTx.getOutput(2));
} }
printInputs("tx", tx); // Add all inputs from taker and apply signature
log.trace("tx = " + tx); for (TransactionOutput connectedOutputForInput : takersConnectedOutputsForAllInputs) {
log.trace("Wallet balance before signing: " + wallet.getBalance()); TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
// sign the input // We grab the signatures from the takersDepositTx and apply it to the new tx input
TransactionInput input = tx.getInput(0); Optional<TransactionInput> result = takersDepositTx.getInputs().stream()
if (input == null || input.getConnectedOutput() == null) { .filter(e -> e.getConnectedOutput().hashCode() == connectedOutputForInput.hashCode()).findAny();
log.error("input or input.getConnectedOutput() is null: " + input); if (result.isPresent()) {
TransactionInput signedInput = result.get();
Script script = signedInput.getScriptSig();
TransactionInput transactionInput = new TransactionInput(params, depositTx, script.getProgram(), outPoint);
depositTx.addInput(transactionInput);
}
} }
Script scriptPubKey = input.getConnectedOutput().getScriptPubKey(); // Add all outputs from takersDepositTx to depositTx
ECKey sigKey = input.getOutpoint().getConnectedKey(wallet); takersDepositTx.getOutputs().forEach(depositTx::addOutput);
Sha256Hash hash = tx.hashForSignature(0, scriptPubKey, Transaction.SigHash.ALL, false);
ECKey.ECDSASignature ecSig = sigKey.sign(hash); printInputs("depositTx", depositTx);
TransactionSignature txSig = new TransactionSignature(ecSig, Transaction.SigHash.ALL, false); log.debug("depositTx = " + depositTx);
if (scriptPubKey.isSentToRawPubKey()) {
input.setScriptSig(ScriptBuilder.createInputScript(txSig)); // Offerer inputs are the first inputs (0 -n), so the index of offererInputs and depositTx.getInputs() matches for the number of offererInputs.
} int index = 0;
else if (scriptPubKey.isSentToAddress()) { for (TransactionInput input : offererInputs) {
input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); signInput(depositTx, input, index);
} checkScriptSig(depositTx, input, index);
else { index++;
throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey);
} }
input.getScriptSig().correctlySpends(tx, 0, scriptPubKey); // TODO verify MS, amounts
log.trace("check if it can be correctly spent for input 0 OK"); //Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey);
TransactionInput input1 = tx.getInput(1);
scriptPubKey = input1.getConnectedOutput().getScriptPubKey();
input1.getScriptSig().correctlySpends(tx, 1, scriptPubKey);
log.trace("check if it can be correctly spent for input 1 OK");
/*
IN[0] offerer signed 0.1001
IN[1] taker signed 1.1001
OUT[0] MS (include btc tx fee for payout tx) 1.2001
OUT[1] offerer change
OUT[2] taker change
btc tx fee 0.0001
*/
log.trace("verify ");
tx.verify();
printInputs("tx", tx);
log.debug("tx = " + tx);
verifyTransaction(depositTx);
checkWalletConsistency();
// Broadcast depositTx
log.trace("Wallet balance before broadcastTransaction: " + wallet.getBalance()); log.trace("Wallet balance before broadcastTransaction: " + wallet.getBalance());
log.trace("Check if wallet is consistent before broadcastTransaction: result=" + wallet.isConsistent()); log.trace("Check if wallet is consistent before broadcastTransaction: result=" + wallet.isConsistent());
ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(tx); ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(depositTx);
log.trace("Wallet balance after broadcastTransaction: " + wallet.getBalance()); log.trace("Wallet balance after broadcastTransaction: " + wallet.getBalance());
log.trace("Check if wallet is consistent: result=" + wallet.isConsistent()); log.trace("Check if wallet is consistent after broadcastTransaction: result=" + wallet.isConsistent());
Futures.addCallback(broadcastComplete, callback); Futures.addCallback(broadcastComplete, callback);
printInputs("tx", tx);
log.debug("tx = " + tx);
} }
// 4 step deposit tx: Offerer send deposit tx to taker // 4 step deposit tx: Offerer send deposit tx to taker
public Transaction takerCommitDepositTx(Transaction depositTx) { public Transaction takerCommitDepositTx(Transaction depositTx) throws WalletException {
log.trace("takerCommitDepositTx"); log.trace("takerCommitDepositTx");
log.trace("inputs: "); log.trace("inputs: ");
log.trace("depositTx=" + depositTx); log.trace("depositTx=" + depositTx);
// If not recreate the tx we get a null pointer at receivePending // If not recreate the tx we get a null pointer at receivePending
depositTx = new Transaction(params, depositTx.bitcoinSerialize()); //depositTx = new Transaction(params, depositTx.bitcoinSerialize());
log.trace("depositTx=" + depositTx); log.trace("depositTx=" + depositTx);
// boolean isAlreadyInWallet = wallet.maybeCommitTx(depositTx); // boolean isAlreadyInWallet = wallet.maybeCommitTx(depositTx);
//log.trace("isAlreadyInWallet=" + isAlreadyInWallet); //log.trace("isAlreadyInWallet=" + isAlreadyInWallet);
@ -900,6 +823,8 @@ public class WalletService {
wallet.receivePending(depositTx, null, true); wallet.receivePending(depositTx, null, true);
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.getMessage()); log.error(t.getMessage());
t.printStackTrace();
throw new WalletException(t);
} }
return depositTx; return depositTx;
@ -1008,7 +933,7 @@ public class WalletService {
} }
} }
private Script getMultiSigScript(byte[] offererPubKey, byte[] takerPubKey, byte[] arbitratorPubKey) { private Script getMultiSigOutputScript(byte[] offererPubKey, byte[] takerPubKey, byte[] arbitratorPubKey) {
ECKey offererKey = ECKey.fromPublicOnly(offererPubKey); ECKey offererKey = ECKey.fromPublicOnly(offererPubKey);
ECKey takerKey = ECKey.fromPublicOnly(takerPubKey); ECKey takerKey = ECKey.fromPublicOnly(takerPubKey);
ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey); ECKey arbitratorKey = ECKey.fromPublicOnly(arbitratorPubKey);
@ -1049,12 +974,120 @@ 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.");
input.getScriptSig().correctlySpends(transaction, inputIndex, input.getConnectedOutput().getScriptPubKey());
inputIndex++;
} 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);
}
}*/
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 InsufficientMoneyException {
// 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);
printInputs("transaction", transaction);
log.trace("transaction=" + transaction);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Inner classes // Inner classes
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public class TransactionDataResult {
private final List<TransactionOutput> connectedOutputsForAllInputs;
private final List<TransactionOutput> outputs;
private Transaction depositTx;
public TransactionDataResult(List<TransactionOutput> connectedOutputsForAllInputs, List<TransactionOutput> outputs) {
this.connectedOutputsForAllInputs = connectedOutputsForAllInputs;
this.outputs = outputs;
}
public TransactionDataResult(Transaction depositTx, List<TransactionOutput> connectedOutputsForAllInputs, List<TransactionOutput> outputs) {
this.depositTx = depositTx;
this.connectedOutputsForAllInputs = connectedOutputsForAllInputs;
this.outputs = outputs;
}
public List<TransactionOutput> getOutputs() {
return outputs;
}
public List<TransactionOutput> getConnectedOutputsForAllInputs() {
return connectedOutputsForAllInputs;
}
public Transaction getDepositTx() {
return depositTx;
}
}
private static class ObservableDownloadListener extends DownloadListener { private static class ObservableDownloadListener extends DownloadListener {
private final Subject<Double, Double> subject = BehaviorSubject.create(0d); private final Subject<Double, Double> subject = BehaviorSubject.create(0d);

View file

@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc.exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SigningException extends Exception {
private static final Logger log = LoggerFactory.getLogger(SigningException.class);
public SigningException(String message) {
super(message);
}
public SigningException(Throwable t) {
super(t);
}
}

View file

@ -0,0 +1,29 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc.exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TransactionVerificationException extends Exception {
private static final Logger log = LoggerFactory.getLogger(TransactionVerificationException.class);
public TransactionVerificationException(Throwable t) {
super(t);
}
}

View file

@ -0,0 +1,29 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.btc.exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WalletException extends Exception {
private static final Logger log = LoggerFactory.getLogger(WalletException.class);
public WalletException(Throwable t) {
super(t);
}
}

View file

@ -26,7 +26,7 @@ import io.bitsquare.trade.protocol.placeoffer.tasks.BroadcastCreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.CreateOfferFeeTx; import io.bitsquare.trade.protocol.placeoffer.tasks.CreateOfferFeeTx;
import io.bitsquare.trade.protocol.placeoffer.tasks.ValidateOffer; import io.bitsquare.trade.protocol.placeoffer.tasks.ValidateOffer;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererProtocol; import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererProtocol;
import io.bitsquare.trade.protocol.trade.offerer.tasks.PrepareDepositTx; import io.bitsquare.trade.protocol.trade.offerer.tasks.GetOffererDepositTxInputs;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessPayoutTxPublishedMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestOffererPublishDepositTxMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestOffererPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestTakeOfferMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestTakeOfferMessage;
@ -43,7 +43,7 @@ import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakeOfferFeePayment
import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakerAccount; import io.bitsquare.trade.protocol.trade.offerer.tasks.VerifyTakerAccount;
import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerProtocol; import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerProtocol;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract; import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract;
import io.bitsquare.trade.protocol.trade.taker.tasks.PayDeposit; import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCreatesAndSignsDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.PayTakeOfferFee; import io.bitsquare.trade.protocol.trade.taker.tasks.PayTakeOfferFee;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage;
@ -51,7 +51,7 @@ import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRespondToTakeOfferRe
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRequestDepositPaymentMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRequestDepositPaymentMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.RequestTakeOffer; import io.bitsquare.trade.protocol.trade.taker.tasks.RequestTakeOffer;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer; import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendSignedTakerDepositTxAsHex; import io.bitsquare.trade.protocol.trade.taker.tasks.SendSignedTakerDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendTakeOfferFeePayedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.SendTakeOfferFeePayedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.SignAndPublishPayoutTx; import io.bitsquare.trade.protocol.trade.taker.tasks.SignAndPublishPayoutTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx; import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx;
@ -110,7 +110,7 @@ public class DebugView extends InitializableView {
RespondToTakeOfferRequest.class, RespondToTakeOfferRequest.class,
ProcessTakeOfferFeePayedMessage.class, ProcessTakeOfferFeePayedMessage.class,
PrepareDepositTx.class, GetOffererDepositTxInputs.class,
RequestDepositPayment.class, RequestDepositPayment.class,
ProcessRequestOffererPublishDepositTxMessage.class, ProcessRequestOffererPublishDepositTxMessage.class,
@ -140,8 +140,8 @@ public class DebugView extends InitializableView {
ProcessRequestDepositPaymentMessage.class, ProcessRequestDepositPaymentMessage.class,
VerifyOffererAccount.class, VerifyOffererAccount.class,
CreateAndSignContract.class, CreateAndSignContract.class,
PayDeposit.class, TakerCreatesAndSignsDepositTx.class,
SendSignedTakerDepositTxAsHex.class, SendSignedTakerDepositTx.class,
ProcessDepositTxPublishedMessage.class, ProcessDepositTxPublishedMessage.class,
TakerCommitDepositTx.class, TakerCommitDepositTx.class,

View file

@ -18,6 +18,7 @@
package io.bitsquare.trade.protocol.trade; package io.bitsquare.trade.protocol.trade;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.BlockChainService; import io.bitsquare.btc.BlockChainService;
import io.bitsquare.btc.WalletService; import io.bitsquare.btc.WalletService;
import io.bitsquare.crypto.SignatureService; import io.bitsquare.crypto.SignatureService;
@ -50,6 +51,9 @@ public class OfferSharedModel extends SharedModel {
protected final ECKey accountKey; protected final ECKey accountKey;
protected final byte[] arbitratorPubKey; protected final byte[] arbitratorPubKey;
// lazy initialized at first read access, as we don't want to create an entry before it is really needed
protected AddressEntry addressInfo;
// data written/read by tasks // data written/read by tasks
protected TradeMessage tradeMessage; protected TradeMessage tradeMessage;
protected byte[] takerPubKey; protected byte[] takerPubKey;
@ -77,6 +81,28 @@ public class OfferSharedModel extends SharedModel {
} }
// getter/setter // getter/setter
public AddressEntry getAddressInfo() {
if (addressInfo == null)
addressInfo = getWalletService().getAddressInfo(offer.getId());
return addressInfo;
}
public String getPeersAccountId() {
return peersAccountId;
}
public void setPeersAccountId(String peersAccountId) {
this.peersAccountId = peersAccountId;
}
public BankAccount getPeersBankAccount() {
return peersBankAccount;
}
public void setPeersBankAccount(BankAccount peersBankAccount) {
this.peersBankAccount = peersBankAccount;
}
public TradeMessageService getTradeMessageService() { public TradeMessageService getTradeMessageService() {
return tradeMessageService; return tradeMessageService;

View file

@ -31,9 +31,12 @@ import io.bitsquare.user.User;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -52,28 +55,25 @@ public class BuyerAsOffererModel extends OfferSharedModel {
private Trade trade; private Trade trade;
private Peer taker; private Peer taker;
private Transaction preparedDepositTx;
private Transaction depositTx;
private String takerAccountId; private String takerAccountId;
private BankAccount takerBankAccount; private BankAccount takerBankAccount;
private PublicKey takerMessagePublicKey; private PublicKey takerMessagePublicKey;
private String takerContractAsJson; private String takerContractAsJson;
private Transaction takersSignedDepositTx;
private Transaction takersFromTx;
private byte[] txScriptSig;
private long takerTxOutIndex;
private Coin takerPaybackAmount; private Coin takerPaybackAmount;
private String takeOfferFeeTxId; private String takeOfferFeeTxId;
private String takerPayoutAddress;
private long offererTxOutIndex;
private byte[] offererPubKey; private byte[] offererPubKey;
private ECKey.ECDSASignature offererSignature; private ECKey.ECDSASignature offererSignature;
private Coin offererPaybackAmount; private Coin offererPaybackAmount;
private List<TransactionOutput> offererConnectedOutputsForAllInputs;
private List<TransactionOutput> offererOutputs;
private Transaction takerDepositTx;
private List<TransactionOutput> takerConnectedOutputsForAllInputs;
private List<TransactionOutput> takerOutputs;
private String takerPayoutAddress;
private Transaction offererPayoutTx;
private Transaction publishedDepositTx;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -110,21 +110,6 @@ public class BuyerAsOffererModel extends OfferSharedModel {
return offererPaybackAddress; return offererPaybackAddress;
} }
public Transaction getPreparedDepositTx() {
return preparedDepositTx;
}
public void setPreparedDepositTx(Transaction preparedDepositTx) {
this.preparedDepositTx = preparedDepositTx;
}
public long getOffererTxOutIndex() {
return offererTxOutIndex;
}
public void setOffererTxOutIndex(long offererTxOutIndex) {
this.offererTxOutIndex = offererTxOutIndex;
}
public String getTakeOfferFeeTxId() { public String getTakeOfferFeeTxId() {
return takeOfferFeeTxId; return takeOfferFeeTxId;
@ -134,14 +119,6 @@ public class BuyerAsOffererModel extends OfferSharedModel {
this.takeOfferFeeTxId = takeOfferFeeTxId; this.takeOfferFeeTxId = takeOfferFeeTxId;
} }
public String getTakerPayoutAddress() {
return takerPayoutAddress;
}
public void setTakerPayoutAddress(String takerPayoutAddress) {
this.takerPayoutAddress = takerPayoutAddress;
}
@Override @Override
public String getTakerAccountId() { public String getTakerAccountId() {
return takerAccountId; return takerAccountId;
@ -178,37 +155,6 @@ public class BuyerAsOffererModel extends OfferSharedModel {
this.takerContractAsJson = takerContractAsJson; this.takerContractAsJson = takerContractAsJson;
} }
public Transaction getTakersSignedDepositTx() {
return takersSignedDepositTx;
}
public void setTakersSignedDepositTx(Transaction takersSignedDepositTx) {
this.takersSignedDepositTx = takersSignedDepositTx;
}
public Transaction getTakersFromTx() {
return takersFromTx;
}
public void setTakersFromTx(Transaction takersFromTx) {
this.takersFromTx = takersFromTx;
}
public byte[] getTxScriptSig() {
return txScriptSig;
}
public void setTxScriptSig(byte[] txScriptSig) {
this.txScriptSig = txScriptSig;
}
public long getTakerTxOutIndex() {
return takerTxOutIndex;
}
public void setTakerTxOutIndex(long takerTxOutIndex) {
this.takerTxOutIndex = takerTxOutIndex;
}
public byte[] getOffererPubKey() { public byte[] getOffererPubKey() {
return offererPubKey; return offererPubKey;
@ -218,13 +164,6 @@ public class BuyerAsOffererModel extends OfferSharedModel {
this.offererPubKey = offererPubKey; this.offererPubKey = offererPubKey;
} }
public Transaction getDepositTx() {
return depositTx;
}
public void setDepositTx(Transaction depositTx) {
this.depositTx = depositTx;
}
public ECKey.ECDSASignature getOffererSignature() { public ECKey.ECDSASignature getOffererSignature() {
return offererSignature; return offererSignature;
@ -261,4 +200,68 @@ public class BuyerAsOffererModel extends OfferSharedModel {
public void setTaker(Peer taker) { public void setTaker(Peer taker) {
this.taker = taker; this.taker = taker;
} }
public List<TransactionOutput> getOffererConnectedOutputsForAllInputs() {
return offererConnectedOutputsForAllInputs;
}
public void setOffererConnectedOutputsForAllInputs(List<TransactionOutput> offererConnectedOutputsForAllInputs) {
this.offererConnectedOutputsForAllInputs = offererConnectedOutputsForAllInputs;
}
public List<TransactionOutput> getOffererOutputs() {
return offererOutputs;
}
public void setOffererOutputs(List<TransactionOutput> offererOutputs) {
this.offererOutputs = offererOutputs;
}
public void setTakerDepositTx(Transaction takerDepositTx) {
this.takerDepositTx = takerDepositTx;
}
public Transaction getTakerDepositTx() {
return takerDepositTx;
}
public void setTakerConnectedOutputsForAllInputs(List<TransactionOutput> takerConnectedOutputsForAllInputs) {
this.takerConnectedOutputsForAllInputs = takerConnectedOutputsForAllInputs;
}
public List<TransactionOutput> getTakerConnectedOutputsForAllInputs() {
return takerConnectedOutputsForAllInputs;
}
public void setTakerOutputs(List<TransactionOutput> takerOutputs) {
this.takerOutputs = takerOutputs;
}
public List<TransactionOutput> getTakerOutputs() {
return takerOutputs;
}
public String getTakerPayoutAddress() {
return takerPayoutAddress;
}
public void setTakerPayoutAddress(String takerPayoutAddress) {
this.takerPayoutAddress = takerPayoutAddress;
}
public void setOffererPayoutTx(Transaction offererPayoutTx) {
this.offererPayoutTx = offererPayoutTx;
}
public Transaction getOffererPayoutTx() {
return offererPayoutTx;
}
public void setPublishedDepositTx(Transaction publishedDepositTx) {
this.publishedDepositTx = publishedDepositTx;
}
public Transaction getPublishedDepositTx() {
return publishedDepositTx;
}
} }

View file

@ -21,7 +21,7 @@ import io.bitsquare.network.Message;
import io.bitsquare.network.Peer; import io.bitsquare.network.Peer;
import io.bitsquare.trade.handlers.MessageHandler; import io.bitsquare.trade.handlers.MessageHandler;
import io.bitsquare.trade.protocol.trade.TradeMessage; import io.bitsquare.trade.protocol.trade.TradeMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.PrepareDepositTx; import io.bitsquare.trade.protocol.trade.offerer.tasks.GetOffererDepositTxInputs;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessPayoutTxPublishedMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessPayoutTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestOffererPublishDepositTxMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestOffererPublishDepositTxMessage;
import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestTakeOfferMessage; import io.bitsquare.trade.protocol.trade.offerer.tasks.ProcessRequestTakeOfferMessage;
@ -110,7 +110,7 @@ public class BuyerAsOffererProtocol {
); );
taskRunner.addTasks( taskRunner.addTasks(
ProcessTakeOfferFeePayedMessage.class, ProcessTakeOfferFeePayedMessage.class,
PrepareDepositTx.class, GetOffererDepositTxInputs.class,
RequestDepositPayment.class RequestDepositPayment.class
); );
taskRunner.run(); taskRunner.run();

View file

@ -20,29 +20,34 @@ package io.bitsquare.trade.protocol.trade.offerer.messages;
import io.bitsquare.bank.BankAccount; import io.bitsquare.bank.BankAccount;
import io.bitsquare.trade.protocol.trade.TradeMessage; import io.bitsquare.trade.protocol.trade.TradeMessage;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.TransactionOutput;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
public class RequestDepositPaymentMessage implements Serializable, TradeMessage { public class RequestDepositPaymentMessage implements Serializable, TradeMessage {
private static final long serialVersionUID = -3988720410493712913L; private static final long serialVersionUID = -3988720410493712913L;
private final String tradeId; private final String tradeId;
private final List<TransactionOutput> offererConnectedOutputsForAllInputs;
private final List<TransactionOutput> offererOutputs;
private final byte[] offererPubKey;
private final BankAccount bankAccount; private final BankAccount bankAccount;
private final String accountID; private final String accountID;
private final byte[] offererPubKey;
private final Transaction preparedDepositTx;
private final long offererTxOutIndex;
public RequestDepositPaymentMessage(String tradeId, BankAccount bankAccount, String accountID, public RequestDepositPaymentMessage(String tradeId,
byte[] offererPubKey, Transaction preparedDepositTx, List<TransactionOutput> offererConnectedOutputsForAllInputs,
long offererTxOutIndex) { List<TransactionOutput> offererOutputs,
byte[] offererPubKey,
BankAccount bankAccount,
String accountID) {
this.tradeId = tradeId; this.tradeId = tradeId;
this.offererConnectedOutputsForAllInputs = offererConnectedOutputsForAllInputs;
this.offererOutputs = offererOutputs;
this.offererPubKey = offererPubKey;
this.bankAccount = bankAccount; this.bankAccount = bankAccount;
this.accountID = accountID; this.accountID = accountID;
this.offererPubKey = offererPubKey;
this.preparedDepositTx = preparedDepositTx;
this.offererTxOutIndex = offererTxOutIndex;
} }
@Override @Override
@ -50,6 +55,18 @@ public class RequestDepositPaymentMessage implements Serializable, TradeMessage
return tradeId; return tradeId;
} }
public List<TransactionOutput> getOffererConnectedOutputsForAllInputs() {
return offererConnectedOutputsForAllInputs;
}
public List<TransactionOutput> getOffererOutputs() {
return offererOutputs;
}
public byte[] getOffererPubKey() {
return offererPubKey;
}
public BankAccount getBankAccount() { public BankAccount getBankAccount() {
return bankAccount; return bankAccount;
} }
@ -57,16 +74,4 @@ public class RequestDepositPaymentMessage implements Serializable, TradeMessage
public String getAccountId() { public String getAccountId() {
return accountID; return accountID;
} }
public byte[] getOffererPubKey() {
return offererPubKey;
}
public Transaction getPreparedDepositTx() {
return preparedDepositTx;
}
public long getOffererTxOutIndex() {
return offererTxOutIndex;
}
} }

View file

@ -17,45 +17,37 @@
package io.bitsquare.trade.protocol.trade.offerer.tasks; package io.bitsquare.trade.protocol.trade.offerer.tasks;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel; import io.bitsquare.trade.protocol.trade.offerer.BuyerAsOffererModel;
import io.bitsquare.util.taskrunner.Task; import io.bitsquare.util.taskrunner.Task;
import io.bitsquare.util.taskrunner.TaskRunner; import io.bitsquare.util.taskrunner.TaskRunner;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class PrepareDepositTx extends Task<BuyerAsOffererModel> { public class GetOffererDepositTxInputs extends Task<BuyerAsOffererModel> {
private static final Logger log = LoggerFactory.getLogger(PrepareDepositTx.class); private static final Logger log = LoggerFactory.getLogger(GetOffererDepositTxInputs.class);
public PrepareDepositTx(TaskRunner taskHandler, BuyerAsOffererModel model) { public GetOffererDepositTxInputs(TaskRunner taskHandler, BuyerAsOffererModel model) {
super(taskHandler, model); super(taskHandler, model);
} }
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
byte[] offererPubKey = model.getWalletService().getAddressInfo(model.getTrade().getId()).getPubKey();
Coin offererInputAmount = model.getTrade().getSecurityDeposit().add(FeePolicy.TX_FEE); Coin offererInputAmount = model.getTrade().getSecurityDeposit().add(FeePolicy.TX_FEE);
Transaction transaction = model.getWalletService().offererPreparesDepositTx( AddressEntry addressInfo = model.getWalletService().getAddressInfo(model.getTrade().getId());
offererInputAmount, WalletService.TransactionDataResult result = model.getWalletService().offererCreatesDepositTxInputs(offererInputAmount, addressInfo);
model.getTrade().getId(),
offererPubKey, model.setOffererConnectedOutputsForAllInputs(result.getConnectedOutputsForAllInputs());
model.getTakerPubKey(), model.setOffererOutputs(result.getOutputs());
model.getArbitratorPubKey());
long offererTxOutIndex = transaction.getInput(0).getOutpoint().getIndex();
model.setOffererPubKey(offererPubKey);
model.setPreparedDepositTx(transaction);
model.setOffererTxOutIndex(offererTxOutIndex);
complete(); complete();
} catch (InsufficientMoneyException e) { } catch (Throwable e) {
failed(e); failed(e);
} }
} }

View file

@ -25,7 +25,7 @@ import io.bitsquare.util.taskrunner.TaskRunner;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.*;
import static io.bitsquare.util.Validator.*; import static io.bitsquare.util.Validator.*;
public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOffererModel> { public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOffererModel> {
@ -39,17 +39,16 @@ public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOf
protected void doRun() { protected void doRun() {
try { try {
checkTradeId(model.getTrade().getId(), model.getTradeMessage()); checkTradeId(model.getTrade().getId(), model.getTradeMessage());
RequestOffererPublishDepositTxMessage message = (RequestOffererPublishDepositTxMessage) model.getTradeMessage(); RequestOffererPublishDepositTxMessage message = (RequestOffererPublishDepositTxMessage) model.getTradeMessage();
model.setTakerPayoutAddress(nonEmptyStringOf(message.getTakerPayoutAddress()));
model.setTakerAccountId(nonEmptyStringOf(message.getTakerAccountId()));
model.setTakerBankAccount(checkNotNull(message.getTakerBankAccount())); model.setTakerBankAccount(checkNotNull(message.getTakerBankAccount()));
model.setTakerAccountId(nonEmptyStringOf(message.getTakerAccountId()));
model.setTakerMessagePublicKey(checkNotNull(message.getTakerMessagePublicKey())); model.setTakerMessagePublicKey(checkNotNull(message.getTakerMessagePublicKey()));
model.setTakerContractAsJson(nonEmptyStringOf(message.getTakerContractAsJson())); model.setTakerContractAsJson(nonEmptyStringOf(message.getTakerContractAsJson()));
model.setTakersSignedDepositTx(checkNotNull(message.getTakersSignedDepositTx())); model.setTakerDepositTx(checkNotNull(message.getTakersDepositTx()));
model.setTakersFromTx(checkNotNull(message.getTakersFromTx())); model.setTakerConnectedOutputsForAllInputs(checkNotNull(message.getTakersConnectedOutputsForAllInputs()));
model.setTxScriptSig(checkNotNull(message.getTxScriptSig())); checkArgument(message.getTakersConnectedOutputsForAllInputs().size() > 0);
model.setTakerTxOutIndex(nonNegativeLongOf(message.getTakerTxOutIndex())); model.setTakerOutputs(checkNotNull(message.getTakerOutputs()));
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View file

@ -36,9 +36,6 @@ public class ProcessRequestTakeOfferMessage extends Task<BuyerAsOffererModel> {
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
log.debug("######### " + model.getOffer().getId());
log.debug("######### " + model.getTradeMessage().getTradeId());
checkTradeId(model.getOffer().getId(), model.getTradeMessage()); checkTradeId(model.getOffer().getId(), model.getTradeMessage());
complete(); complete();

View file

@ -40,7 +40,6 @@ public class ProcessTakeOfferFeePayedMessage extends Task<BuyerAsOffererModel> {
protected void doRun() { protected void doRun() {
try { try {
checkTradeId(model.getTrade().getId(), model.getTradeMessage()); checkTradeId(model.getTrade().getId(), model.getTradeMessage());
Trade trade = model.getTrade(); Trade trade = model.getTrade();
TakeOfferFeePayedMessage takeOfferFeePayedMessage = (TakeOfferFeePayedMessage) model.getTradeMessage(); TakeOfferFeePayedMessage takeOfferFeePayedMessage = (TakeOfferFeePayedMessage) model.getTradeMessage();
trade.setTakeOfferFeeTxID(nonEmptyStringOf(takeOfferFeePayedMessage.getTakeOfferFeeTxId())); trade.setTakeOfferFeeTxID(nonEmptyStringOf(takeOfferFeePayedMessage.getTakeOfferFeeTxId()));

View file

@ -35,13 +35,14 @@ public class RequestDepositPayment extends Task<BuyerAsOffererModel> {
@Override @Override
protected void doRun() { protected void doRun() {
byte[] offererPubKey = model.getWalletService().getAddressInfo(model.getTrade().getId()).getPubKey();
RequestDepositPaymentMessage tradeMessage = new RequestDepositPaymentMessage( RequestDepositPaymentMessage tradeMessage = new RequestDepositPaymentMessage(
model.getTrade().getId(), model.getTrade().getId(),
model.getOffererConnectedOutputsForAllInputs(),
model.getOffererOutputs(),
offererPubKey,
model.getBankAccount(), model.getBankAccount(),
model.getAccountId(), model.getAccountId());
model.getOffererPubKey(),
model.getPreparedDepositTx(),
model.getOffererTxOutIndex());
model.getTradeMessageService().sendMessage(model.getTaker(), tradeMessage, new SendMessageListener() { model.getTradeMessageService().sendMessage(model.getTaker(), tradeMessage, new SendMessageListener() {
@Override @Override

View file

@ -39,7 +39,7 @@ public class RespondToTakeOfferRequest extends Task<BuyerAsOffererModel> {
@Override @Override
protected void doRun() { protected void doRun() {
offerIsAvailable = model.getOpenOffer().getState() == OpenOffer.State.OPEN; offerIsAvailable = model.getOpenOffer().getState() == OpenOffer.State.OPEN;
if (offerIsAvailable) { if (offerIsAvailable) {
Trade trade = new Trade(model.getOpenOffer().getOffer()); Trade trade = new Trade(model.getOpenOffer().getOffer());
model.setTrade(trade); model.setTrade(trade);

View file

@ -37,7 +37,7 @@ public class SendBankTransferStartedMessage extends Task<BuyerAsOffererModel> {
protected void doRun() { protected void doRun() {
BankTransferStartedMessage tradeMessage = new BankTransferStartedMessage( BankTransferStartedMessage tradeMessage = new BankTransferStartedMessage(
model.getTrade().getId(), model.getTrade().getId(),
model.getDepositTx(), model.getPublishedDepositTx(),
model.getOffererSignature().encodeToDER(), model.getOffererSignature().encodeToDER(),
model.getOffererPaybackAmount(), model.getOffererPaybackAmount(),
model.getTakerPaybackAmount(), model.getTakerPaybackAmount(),

View file

@ -35,8 +35,7 @@ public class SendDepositTxIdToTaker extends Task<BuyerAsOffererModel> {
@Override @Override
protected void doRun() { protected void doRun() {
DepositTxPublishedMessage tradeMessage = new DepositTxPublishedMessage(model.getTrade().getId(), DepositTxPublishedMessage tradeMessage = new DepositTxPublishedMessage(model.getTrade().getId(), model.getTrade().getDepositTx());
model.getTrade().getDepositTx());
model.getTradeMessageService().sendMessage(model.getTaker(), tradeMessage, new SendMessageListener() { model.getTradeMessageService().sendMessage(model.getTaker(), tradeMessage, new SendMessageListener() {
@Override @Override

View file

@ -42,17 +42,18 @@ public class SignAndPublishDepositTx extends Task<BuyerAsOffererModel> {
protected void doRun() { protected void doRun() {
try { try {
model.getWalletService().offererSignAndPublishTx( model.getWalletService().offererSignAndPublishTx(
model.getPreparedDepositTx(), model.getTakerDepositTx(),
model.getTakersSignedDepositTx(), model.getTakerConnectedOutputsForAllInputs(),
model.getTakersFromTx(), model.getOffererConnectedOutputsForAllInputs(),
model.getTxScriptSig(), model.getOffererPubKey(),
model.getOffererTxOutIndex(), model.getTakerPubKey(),
model.getTakerTxOutIndex(), model.getArbitratorPubKey(),
new FutureCallback<Transaction>() { new FutureCallback<Transaction>() {
@Override @Override
public void onSuccess(Transaction transaction) { public void onSuccess(Transaction transaction) {
log.trace("offererSignAndPublishTx succeeded " + transaction); log.trace("offererSignAndPublishTx succeeded " + transaction);
model.setPublishedDepositTx(transaction);
model.getTrade().setDepositTx(transaction); model.getTrade().setDepositTx(transaction);
model.getTrade().setState(Trade.State.DEPOSIT_PUBLISHED); model.getTrade().setState(Trade.State.DEPOSIT_PUBLISHED);

View file

@ -53,7 +53,7 @@ public class SignPayoutTx extends Task<BuyerAsOffererModel> {
model.getTakerPayoutAddress(), model.getTakerPayoutAddress(),
model.getTrade().getId()); model.getTrade().getId());
model.setDepositTx(result.getValue()); model.setOffererPayoutTx(result.getValue());
model.setOffererSignature(result.getKey()); model.setOffererSignature(result.getKey());
model.setOffererPaybackAmount(offererPaybackAmount); model.setOffererPaybackAmount(offererPaybackAmount);
model.setTakerPaybackAmount(takerPaybackAmount); model.setTakerPaybackAmount(takerPaybackAmount);

View file

@ -29,6 +29,9 @@ import io.bitsquare.user.User;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -38,6 +41,7 @@ public class SellerAsTakerModel extends OfferSharedModel {
// provided // provided
private final Trade trade; private final Trade trade;
// written/read by task // written/read by task
private Peer offerer; private Peer offerer;
@ -52,6 +56,12 @@ public class SellerAsTakerModel extends OfferSharedModel {
private ECKey.ECDSASignature offererSignature; private ECKey.ECDSASignature offererSignature;
private Coin offererPaybackAmount; private Coin offererPaybackAmount;
private String offererPayoutAddress; private String offererPayoutAddress;
private List<TransactionOutput> offererConnectedOutputsForAllInputs;
private List<TransactionOutput> offererOutputs;
private List<TransactionOutput> takerConnectedOutputsForAllInputs;
private List<TransactionOutput> takerOutputs;
private Transaction takerDepositTx;
private Transaction publishedDepositTx;
public SellerAsTakerModel(Trade trade, public SellerAsTakerModel(Trade trade,
TradeMessageService tradeMessageService, TradeMessageService tradeMessageService,
@ -68,9 +78,29 @@ public class SellerAsTakerModel extends OfferSharedModel {
this.trade = trade; this.trade = trade;
takerPubKey = walletService.getAddressInfo(trade.getId()).getPubKey(); takerPubKey = walletService.getAddressInfo(trade.getId()).getPubKey();
} }
// getter/setter // getter/setter
public void setOffererPubKey(byte[] offererPubKey) {
this.offererPubKey = offererPubKey;
}
public List<TransactionOutput> getOffererConnectedOutputsForAllInputs() {
return offererConnectedOutputsForAllInputs;
}
public void setOffererConnectedOutputsForAllInputs(List<TransactionOutput> offererConnectedOutputsForAllInputs) {
this.offererConnectedOutputsForAllInputs = offererConnectedOutputsForAllInputs;
}
public List<TransactionOutput> getOffererOutputs() {
return offererOutputs;
}
public void setOffererOutputs(List<TransactionOutput> offererOutputs) {
this.offererOutputs = offererOutputs;
}
public Trade getTrade() { public Trade getTrade() {
return trade; return trade;
} }
@ -103,9 +133,6 @@ public class SellerAsTakerModel extends OfferSharedModel {
return offererPubKey; return offererPubKey;
} }
public void setOffererPubKeyAsHex(byte[] offererPubKey) {
this.offererPubKey = offererPubKey;
}
public Transaction getPreparedDepositTx() { public Transaction getPreparedDepositTx() {
return preparedDepositTx; return preparedDepositTx;
@ -172,4 +199,35 @@ public class SellerAsTakerModel extends OfferSharedModel {
} }
public void setTakerConnectedOutputsForAllInputs(List<TransactionOutput> takerConnectedOutputsForAllInputs) {
this.takerConnectedOutputsForAllInputs = takerConnectedOutputsForAllInputs;
}
public List<TransactionOutput> getTakerConnectedOutputsForAllInputs() {
return takerConnectedOutputsForAllInputs;
}
public void setTakerOutputs(List<TransactionOutput> takerOutputs) {
this.takerOutputs = takerOutputs;
}
public List<TransactionOutput> getTakerOutputs() {
return takerOutputs;
}
public void setTakerDepositTx(Transaction takerDepositTx) {
this.takerDepositTx = takerDepositTx;
}
public Transaction getTakerDepositTx() {
return takerDepositTx;
}
public void setPublishedDepositTx(Transaction publishedDepositTx) {
this.publishedDepositTx = publishedDepositTx;
}
public Transaction getPublishedDepositTx() {
return publishedDepositTx;
}
} }

View file

@ -28,7 +28,7 @@ import io.bitsquare.trade.protocol.trade.offerer.messages.RequestDepositPaymentM
import io.bitsquare.trade.protocol.trade.offerer.messages.RespondToTakeOfferRequestMessage; import io.bitsquare.trade.protocol.trade.offerer.messages.RespondToTakeOfferRequestMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract; import io.bitsquare.trade.protocol.trade.taker.tasks.CreateAndSignContract;
import io.bitsquare.trade.protocol.trade.taker.tasks.GetPeerAddress; import io.bitsquare.trade.protocol.trade.taker.tasks.GetPeerAddress;
import io.bitsquare.trade.protocol.trade.taker.tasks.PayDeposit; import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCreatesAndSignsDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.PayTakeOfferFee; import io.bitsquare.trade.protocol.trade.taker.tasks.PayTakeOfferFee;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessBankTransferStartedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessDepositTxPublishedMessage;
@ -36,7 +36,7 @@ import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRequestDepositPaymen
import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRespondToTakeOfferRequestMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.ProcessRespondToTakeOfferRequestMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.RequestTakeOffer; import io.bitsquare.trade.protocol.trade.taker.tasks.RequestTakeOffer;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer; import io.bitsquare.trade.protocol.trade.taker.tasks.SendPayoutTxToOfferer;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendSignedTakerDepositTxAsHex; import io.bitsquare.trade.protocol.trade.taker.tasks.SendSignedTakerDepositTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.SendTakeOfferFeePayedMessage; import io.bitsquare.trade.protocol.trade.taker.tasks.SendTakeOfferFeePayedMessage;
import io.bitsquare.trade.protocol.trade.taker.tasks.SignAndPublishPayoutTx; import io.bitsquare.trade.protocol.trade.taker.tasks.SignAndPublishPayoutTx;
import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx; import io.bitsquare.trade.protocol.trade.taker.tasks.TakerCommitDepositTx;
@ -129,8 +129,8 @@ public class SellerAsTakerProtocol {
ProcessRequestDepositPaymentMessage.class, ProcessRequestDepositPaymentMessage.class,
VerifyOffererAccount.class, VerifyOffererAccount.class,
CreateAndSignContract.class, CreateAndSignContract.class,
PayDeposit.class, TakerCreatesAndSignsDepositTx.class,
SendSignedTakerDepositTxAsHex.class SendSignedTakerDepositTx.class
); );
taskRunner.run(); taskRunner.run();
} }

View file

@ -21,53 +21,44 @@ import io.bitsquare.bank.BankAccount;
import io.bitsquare.trade.protocol.trade.TradeMessage; import io.bitsquare.trade.protocol.trade.TradeMessage;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionOutput;
import java.io.Serializable; import java.io.Serializable;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.List;
public class RequestOffererPublishDepositTxMessage implements Serializable, TradeMessage { public class RequestOffererPublishDepositTxMessage implements Serializable, TradeMessage {
private static final long serialVersionUID = 2179683654379803071L; private static final long serialVersionUID = 2179683654379803071L;
private final String tradeId; private final String tradeId;
private final BankAccount bankAccount; private final BankAccount bankAccount;
private final String accountID; private final String accountID;
private final PublicKey takerMessagePublicKey; private final PublicKey takerMessagePublicKey;
private final Transaction takersSignedDepositTx;
private final byte[] txScriptSig;
private final Transaction takersFromTx;
private final String contractAsJson; private final String contractAsJson;
private final String takerContractSignature; private final String takerContractSignature;
private final String takerPayoutAddress; private Transaction takersDepositTx;
private final long takerTxOutIndex; private List<TransactionOutput> takersConnectedOutputsForAllInputs;
private List<TransactionOutput> takerOutputs;
private final long offererTxOutIndex;
public RequestOffererPublishDepositTxMessage(String tradeId, public RequestOffererPublishDepositTxMessage(String tradeId,
BankAccount bankAccount, BankAccount bankAccount,
String accountID, String accountID,
PublicKey takerMessagePublicKey, PublicKey takerMessagePublicKey,
Transaction takersSignedDepositTx,
byte[] txScriptSig,
Transaction takersFromTx,
String contractAsJson, String contractAsJson,
String takerContractSignature, String takerContractSignature,
String takerPayoutAddress, Transaction takersDepositTx,
long takerTxOutIndex, List<TransactionOutput> takersConnectedOutputsForAllInputs,
long offererTxOutIndex) { List<TransactionOutput> takerOutputs) {
this.tradeId = tradeId; this.tradeId = tradeId;
this.bankAccount = bankAccount; this.bankAccount = bankAccount;
this.accountID = accountID; this.accountID = accountID;
this.takerMessagePublicKey = takerMessagePublicKey; this.takerMessagePublicKey = takerMessagePublicKey;
this.takersSignedDepositTx = takersSignedDepositTx;
this.txScriptSig = txScriptSig;
this.takersFromTx = takersFromTx;
this.contractAsJson = contractAsJson; this.contractAsJson = contractAsJson;
this.takerContractSignature = takerContractSignature; this.takerContractSignature = takerContractSignature;
this.takerPayoutAddress = takerPayoutAddress; this.takersDepositTx = takersDepositTx;
this.takerTxOutIndex = takerTxOutIndex; this.takersConnectedOutputsForAllInputs = takersConnectedOutputsForAllInputs;
this.offererTxOutIndex = offererTxOutIndex; this.takerOutputs = takerOutputs;
} }
@ -76,10 +67,6 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return tradeId; return tradeId;
} }
public long getOffererTxOutIndex() {
return offererTxOutIndex;
}
public BankAccount getTakerBankAccount() { public BankAccount getTakerBankAccount() {
return bankAccount; return bankAccount;
} }
@ -92,18 +79,6 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return takerMessagePublicKey; return takerMessagePublicKey;
} }
public Transaction getTakersSignedDepositTx() {
return takersSignedDepositTx;
}
public byte[] getTxScriptSig() {
return txScriptSig;
}
public Transaction getTakersFromTx() {
return takersFromTx;
}
public String getTakerContractAsJson() { public String getTakerContractAsJson() {
return contractAsJson; return contractAsJson;
} }
@ -112,12 +87,27 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return takerContractSignature; return takerContractSignature;
} }
public String getTakerPayoutAddress() { public List<TransactionOutput> getTakerOutputs() {
return takerPayoutAddress; return takerOutputs;
} }
public long getTakerTxOutIndex() { public BankAccount getBankAccount() {
return takerTxOutIndex; return bankAccount;
} }
public String getAccountID() {
return accountID;
}
public String getContractAsJson() {
return contractAsJson;
}
public Transaction getTakersDepositTx() {
return takersDepositTx;
}
public List<TransactionOutput> getTakersConnectedOutputsForAllInputs() {
return takersConnectedOutputsForAllInputs;
}
} }

View file

@ -43,13 +43,12 @@ public class ProcessBankTransferStartedMessage extends Task<SellerAsTakerModel>
checkTradeId(model.getTrade().getId(), model.getTradeMessage()); checkTradeId(model.getTrade().getId(), model.getTradeMessage());
BankTransferStartedMessage message = (BankTransferStartedMessage) model.getTradeMessage(); BankTransferStartedMessage message = (BankTransferStartedMessage) model.getTradeMessage();
model.setDepositTx(checkNotNull(message.getDepositTx())); //model.setDepositTx(checkNotNull(message.getDepositTx()));
model.setOffererSignature(checkNotNull(ECKey.ECDSASignature.decodeFromDER(message.getOffererSignature()))); model.setOffererSignature(checkNotNull(ECKey.ECDSASignature.decodeFromDER(message.getOffererSignature())));
model.setOffererPaybackAmount(positiveCoinOf(nonZeroCoinOf(message.getOffererPaybackAmount()))); model.setOffererPaybackAmount(positiveCoinOf(nonZeroCoinOf(message.getOffererPaybackAmount())));
model.setTakerPaybackAmount(positiveCoinOf(nonZeroCoinOf(message.getTakerPaybackAmount()))); model.setTakerPaybackAmount(positiveCoinOf(nonZeroCoinOf(message.getTakerPaybackAmount())));
model.setOffererPayoutAddress(nonEmptyStringOf(message.getOffererPayoutAddress())); model.setOffererPayoutAddress(nonEmptyStringOf(message.getOffererPayoutAddress()));
// TODO listener.onBankTransferInited(message.getTrade().getId());
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);

View file

@ -41,7 +41,7 @@ public class ProcessDepositTxPublishedMessage extends Task<SellerAsTakerModel> {
checkTradeId(model.getTrade().getId(), model.getTradeMessage()); checkTradeId(model.getTrade().getId(), model.getTradeMessage());
DepositTxPublishedMessage message = (DepositTxPublishedMessage) model.getTradeMessage(); DepositTxPublishedMessage message = (DepositTxPublishedMessage) model.getTradeMessage();
model.setDepositTx(checkNotNull(message.getDepositTx())); model.setPublishedDepositTx(checkNotNull(message.getDepositTx()));
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View file

@ -25,7 +25,7 @@ import io.bitsquare.util.taskrunner.TaskRunner;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.*;
import static io.bitsquare.util.Validator.*; import static io.bitsquare.util.Validator.*;
public class ProcessRequestDepositPaymentMessage extends Task<SellerAsTakerModel> { public class ProcessRequestDepositPaymentMessage extends Task<SellerAsTakerModel> {
@ -40,11 +40,13 @@ public class ProcessRequestDepositPaymentMessage extends Task<SellerAsTakerModel
try { try {
checkTradeId(model.getTrade().getId(), model.getTradeMessage()); checkTradeId(model.getTrade().getId(), model.getTradeMessage());
RequestDepositPaymentMessage message = (RequestDepositPaymentMessage) model.getTradeMessage(); RequestDepositPaymentMessage message = (RequestDepositPaymentMessage) model.getTradeMessage();
model.setTakerAccountId(nonEmptyStringOf(message.getAccountId()));
model.setOffererConnectedOutputsForAllInputs(checkNotNull(message.getOffererConnectedOutputsForAllInputs()));
checkArgument(message.getOffererConnectedOutputsForAllInputs().size() > 0);
model.setOffererOutputs(checkNotNull(message.getOffererOutputs()));
model.setOffererPubKey(checkNotNull(message.getOffererPubKey()));
model.setTakerBankAccount(checkNotNull(message.getBankAccount())); model.setTakerBankAccount(checkNotNull(message.getBankAccount()));
model.setOffererPubKeyAsHex(checkNotNull(message.getOffererPubKey())); model.setTakerAccountId(nonEmptyStringOf(message.getAccountId()));
model.setPreparedDepositTx(checkNotNull(message.getPreparedDepositTx()));
model.setOffererTxOutIndex(nonNegativeLongOf(message.getOffererTxOutIndex()));
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View file

@ -36,7 +36,6 @@ public class RequestTakeOffer extends Task<SellerAsTakerModel> {
@Override @Override
protected void doRun() { protected void doRun() {
log.debug("######### " + model.getTrade().getId());
model.getTradeMessageService().sendMessage(model.getOfferer(), new RequestTakeOfferMessage(model.getTrade().getId()), model.getTradeMessageService().sendMessage(model.getOfferer(), new RequestTakeOfferMessage(model.getTrade().getId()),
new SendMessageListener() { new SendMessageListener() {
@Override @Override

View file

@ -23,36 +23,29 @@ import io.bitsquare.trade.protocol.trade.taker.messages.RequestOffererPublishDep
import io.bitsquare.util.taskrunner.Task; import io.bitsquare.util.taskrunner.Task;
import io.bitsquare.util.taskrunner.TaskRunner; import io.bitsquare.util.taskrunner.TaskRunner;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class SendSignedTakerDepositTxAsHex extends Task<SellerAsTakerModel> { public class SendSignedTakerDepositTx extends Task<SellerAsTakerModel> {
private static final Logger log = LoggerFactory.getLogger(SendSignedTakerDepositTxAsHex.class); private static final Logger log = LoggerFactory.getLogger(SendSignedTakerDepositTx.class);
public SendSignedTakerDepositTxAsHex(TaskRunner taskHandler, SellerAsTakerModel model) { public SendSignedTakerDepositTx(TaskRunner taskHandler, SellerAsTakerModel model) {
super(taskHandler, model); super(taskHandler, model);
} }
@Override @Override
protected void doRun() { protected void doRun() {
Transaction takersSignedDepositTx = model.getSignedTakerDepositTx();
long takerTxOutIndex = model.getSignedTakerDepositTx().getInput(1).getOutpoint().getIndex();
RequestOffererPublishDepositTxMessage tradeMessage = new RequestOffererPublishDepositTxMessage( RequestOffererPublishDepositTxMessage tradeMessage = new RequestOffererPublishDepositTxMessage(
model.getTrade().getId(), model.getTrade().getId(),
model.getBankAccount(), model.getBankAccount(),
model.getAccountId(), model.getAccountId(),
model.getMessagePublicKey(), model.getMessagePublicKey(),
takersSignedDepositTx,
takersSignedDepositTx.getInput(1).getScriptBytes(),
takersSignedDepositTx.getInput(1).getConnectedOutput().getParentTransaction(),
model.getTrade().getContractAsJson(), model.getTrade().getContractAsJson(),
model.getTrade().getTakerContractSignature(), model.getTrade().getTakerContractSignature(),
model.getWalletService().getAddressInfo(model.getTrade().getId()).getAddressString(), model.getTakerDepositTx(),
takerTxOutIndex, model.getTakerConnectedOutputsForAllInputs(),
model.getOffererTxOutIndex()); model.getTakerOutputs()
);
model.getTradeMessageService().sendMessage(model.getOfferer(), tradeMessage, new SendMessageListener() { model.getTradeMessageService().sendMessage(model.getOfferer(), tradeMessage, new SendMessageListener() {
@Override @Override

View file

@ -43,7 +43,7 @@ public class SignAndPublishPayoutTx extends Task<SellerAsTakerModel> {
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
model.getWalletService().takerSignsAndSendsTx(model.getDepositTx(), model.getWalletService().takerSignsAndSendsTx(model.getPublishedDepositTx(),
model.getOffererSignature(), model.getOffererSignature(),
model.getOffererPaybackAmount(), model.getOffererPaybackAmount(),
model.getTakerPaybackAmount(), model.getTakerPaybackAmount(),

View file

@ -37,7 +37,7 @@ public class TakerCommitDepositTx extends Task<SellerAsTakerModel> {
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
Transaction transaction = model.getWalletService().takerCommitDepositTx(model.getDepositTx()); Transaction transaction = model.getWalletService().takerCommitDepositTx(model.getPublishedDepositTx());
model.getTrade().setDepositTx(transaction); model.getTrade().setDepositTx(transaction);
model.getTrade().setState(Trade.State.DEPOSIT_PUBLISHED); model.getTrade().setState(Trade.State.DEPOSIT_PUBLISHED);

View file

@ -18,42 +18,46 @@
package io.bitsquare.trade.protocol.trade.taker.tasks; package io.bitsquare.trade.protocol.trade.taker.tasks;
import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.WalletService;
import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerModel; import io.bitsquare.trade.protocol.trade.taker.SellerAsTakerModel;
import io.bitsquare.util.taskrunner.Task; import io.bitsquare.util.taskrunner.Task;
import io.bitsquare.util.taskrunner.TaskRunner; import io.bitsquare.util.taskrunner.TaskRunner;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class PayDeposit extends Task<SellerAsTakerModel> { public class TakerCreatesAndSignsDepositTx extends Task<SellerAsTakerModel> {
private static final Logger log = LoggerFactory.getLogger(PayDeposit.class); private static final Logger log = LoggerFactory.getLogger(TakerCreatesAndSignsDepositTx.class);
public PayDeposit(TaskRunner taskHandler, SellerAsTakerModel model) { public TakerCreatesAndSignsDepositTx(TaskRunner taskHandler, SellerAsTakerModel model) {
super(taskHandler, model); super(taskHandler, model);
} }
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
Coin amountToPay = model.getTrade().getTradeAmount().add(model.getTrade().getSecurityDeposit()); Coin inputAmount = model.getTrade().getTradeAmount().add(model.getTrade().getSecurityDeposit());
Coin msOutputAmount = amountToPay.add(model.getTrade().getSecurityDeposit()).add(FeePolicy.TX_FEE); Coin msOutputAmount = inputAmount.add(model.getTrade().getSecurityDeposit()).add(FeePolicy.TX_FEE);
Transaction signedTakerDepositTx = model.getWalletService().takerAddPaymentAndSignTx(
amountToPay, WalletService.TransactionDataResult result = model.getWalletService().takerCreatesAndSignsDepositTx(
inputAmount,
msOutputAmount, msOutputAmount,
model.getPreparedDepositTx(), model.getOffererConnectedOutputsForAllInputs(),
model.getTrade().getId(), model.getOffererOutputs(),
model.getAddressInfo(),
model.getOffererPubKey(), model.getOffererPubKey(),
model.getTakerPubKey(), model.getTakerPubKey(),
model.getArbitratorPubKey()); model.getArbitratorPubKey());
model.setSignedTakerDepositTx(signedTakerDepositTx);
model.setTakerConnectedOutputsForAllInputs(result.getConnectedOutputsForAllInputs());
model.setTakerOutputs(result.getOutputs());
model.setTakerDepositTx(result.getDepositTx());
complete(); complete();
} catch (InsufficientMoneyException e) { } catch (Exception e) {
failed(e); failed(e);
} }
} }

View file

@ -51,7 +51,6 @@ import org.slf4j.LoggerFactory;
* The TomP2P library codebase shall not be used outside that service. * The TomP2P library codebase shall not be used outside that service.
* That way we limit the dependency of the TomP2P library only to that class (and it's sub components). * That way we limit the dependency of the TomP2P library only to that class (and it's sub components).
* <p/> * <p/>
* TODO: improve callbacks that executor.execute is not necessary. We call usually that methods form teh UI thread.
*/ */
public class TomP2PTradeMessageService implements TradeMessageService { public class TomP2PTradeMessageService implements TradeMessageService {
private static final Logger log = LoggerFactory.getLogger(TomP2PTradeMessageService.class); private static final Logger log = LoggerFactory.getLogger(TomP2PTradeMessageService.class);

View file

@ -17,6 +17,10 @@
package io.bitsquare.util.taskrunner; package io.bitsquare.util.taskrunner;
import io.bitsquare.btc.exceptions.SigningException;
import io.bitsquare.btc.exceptions.TransactionVerificationException;
import io.bitsquare.btc.exceptions.WalletException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -45,7 +49,7 @@ public abstract class Task<T extends SharedModel> {
} }
} }
abstract protected void doRun(); abstract protected void doRun() throws WalletException, TransactionVerificationException, SigningException;
abstract protected void updateStateOnFault(); abstract protected void updateStateOnFault();

View file

@ -38,16 +38,17 @@
<logger name="net.tomp2p" level="ERROR"/> <logger name="net.tomp2p" level="ERROR"/>
<logger name="com.vinumeris.updatefx" level="OFF"/> <logger name="com.vinumeris.updatefx" level="OFF"/>
<logger name="io.netty" level="OFF"/> <logger name="io.netty" level="OFF"/>
<logger name="org.bitcoinj.core.BitcoinSerializer" level="ERROR"/>
<logger name="org.bitcoinj.core.Peer" level="ERROR"/>
<!-- <logger name="net.tomp2p.message.Encoder" level="WARN"/>
<!-- <logger name="net.tomp2p.message.Encoder" level="WARN"/> <logger name="net.tomp2p.message.Decoder" level="WARN"/>
<logger name="net.tomp2p.message.Decoder" level="WARN"/> <logger name="net.tomp2p.message.MessageHeaderCodec" level="WARN"/>
<logger name="net.tomp2p.message.MessageHeaderCodec" level="WARN"/>
<logger name="io.netty.util" level="WARN"/>
<logger name="io.netty.util" level="WARN"/> <logger name="io.netty.channel" level="WARN"/>
<logger name="io.netty.channel" level="WARN"/> <logger name="io.netty.buffer" level="WARN"/>-->
<logger name="io.netty.buffer" level="WARN"/>-->