Fix bug with different tx versions between taker and buyer

This commit is contained in:
Manfred Karrer 2015-03-16 12:38:39 +01:00
parent 25ff3d26f1
commit 3db41e4edf
10 changed files with 193 additions and 128 deletions

View file

@ -89,7 +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 com.google.inject.internal.util.$Preconditions.*;
import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN; import static org.bitcoinj.script.ScriptOpCodes.OP_RETURN;
public class WalletService { public class WalletService {
@ -512,7 +512,7 @@ public class WalletService {
Futures.addCallback(sendResult.broadcastComplete, callback); Futures.addCallback(sendResult.broadcastComplete, callback);
log.debug("Registration transaction: " + tx); log.debug("Registration transaction: " + tx);
printInputs("payRegistrationFee", tx); printTxWithInputs("payRegistrationFee", tx);
} }
public Transaction createOfferFeeTx(String offerId) throws InsufficientMoneyException { public Transaction createOfferFeeTx(String offerId) throws InsufficientMoneyException {
@ -529,7 +529,7 @@ public class WalletService {
sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true); sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry, true);
sendRequest.changeAddress = addressEntry.getAddress(); sendRequest.changeAddress = addressEntry.getAddress();
wallet.completeTx(sendRequest); wallet.completeTx(sendRequest);
printInputs("payCreateOfferFee", tx); printTxWithInputs("payCreateOfferFee", tx);
return tx; return tx;
} }
@ -554,7 +554,7 @@ public class WalletService {
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, callback); Futures.addCallback(sendResult.broadcastComplete, callback);
printInputs("payTakeOfferFee", tx); printTxWithInputs("payTakeOfferFee", tx);
log.debug("tx=" + tx); log.debug("tx=" + tx);
return tx.getHashAsString(); return tx.getHashAsString();
@ -586,7 +586,7 @@ public class WalletService {
Wallet.SendResult sendResult = wallet.sendCoins(sendRequest); Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
Futures.addCallback(sendResult.broadcastComplete, callback); Futures.addCallback(sendResult.broadcastComplete, callback);
printInputs("sendFunds", tx); printTxWithInputs("sendFunds", tx);
log.debug("tx=" + tx); log.debug("tx=" + tx);
return tx.getHashAsString(); return tx.getHashAsString();
@ -612,8 +612,8 @@ public class WalletService {
Transaction dummyTX = new Transaction(params); 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. // 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. // We don't care about fee calculation differences between the real tx and that dummy tx as we use a static tx fee.
TransactionOutput msOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, new ECKey().toAddress(params)); TransactionOutput dummyOutput = new TransactionOutput(params, dummyTX, dummyOutputAmount, new ECKey().toAddress(params));
dummyTX.addOutput(msOutput); dummyTX.addOutput(dummyOutput);
// Fin the needed inputs to pay the output, optional add change output. // 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 // 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
@ -636,8 +636,7 @@ public class WalletService {
OUT[2...n] optional more outputs are supported, but currently there is just max. 1 optional change output OUT[2...n] optional more outputs are supported, but currently there is just max. 1 optional change output
*/ */
printInputs("dummyTX", dummyTX); printTxWithInputs("dummyTX", dummyTX);
log.debug("dummyTX created: " + dummyTX);
List<TransactionOutput> connectedOutputsForAllInputs = new ArrayList<>(); List<TransactionOutput> connectedOutputsForAllInputs = new ArrayList<>();
for (TransactionInput input : dummyTX.getInputs()) { for (TransactionInput input : dummyTX.getInputs()) {
@ -647,7 +646,7 @@ public class WalletService {
// Only save offerer outputs, the MS output is ignored // Only save offerer outputs, the MS output is ignored
List<TransactionOutput> outputs = new ArrayList<>(); List<TransactionOutput> outputs = new ArrayList<>();
for (TransactionOutput output : dummyTX.getOutputs()) { for (TransactionOutput output : dummyTX.getOutputs()) {
if (output.equals(msOutput)) if (output.equals(dummyOutput))
continue; continue;
outputs.add(output); outputs.add(output);
} }
@ -656,7 +655,7 @@ public class WalletService {
} }
// 2. step: Taker creates a deposit tx and signs his inputs // 2. step: Taker creates a deposit tx and signs his inputs
public TransactionDataResult takerCreatesAndSignsDepositTx(Coin inputAmount, public TransactionDataResult takerCreatesAndSignsDepositTx(Coin takerInputAmount,
Coin msOutputAmount, Coin msOutputAmount,
List<TransactionOutput> offererConnectedOutputsForAllInputs, List<TransactionOutput> offererConnectedOutputsForAllInputs,
List<TransactionOutput> offererOutputs, List<TransactionOutput> offererOutputs,
@ -666,68 +665,90 @@ public class WalletService {
byte[] arbitratorPubKey) throws InsufficientMoneyException, SigningException, byte[] arbitratorPubKey) throws InsufficientMoneyException, SigningException,
TransactionVerificationException, WalletException { TransactionVerificationException, WalletException {
// TODO verify amounts, addresses, MS checkArgument(offererConnectedOutputsForAllInputs.size() > 0);
Transaction depositTx = new Transaction(params); // To keep tx consistent with tx used for publishing we use always following ordering of inputs and outputs (offerer first then taker):
Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); /*
// We use temporary inputAmount as the value for the output amount to get the correct inputs from the takers side. IN[0] offerer (mandatory) e.g. 0.1 BTC
// Later when we add the offerer inputs we replace the output amount with the real msOutputAmount IN[...] optional additional offerer inputs (normally never used as we pay from trade fee tx and always have 1 output there)
// Tx fee for deposit tx will be paid by offerer. IN[...] taker (mandatory) e.g. 1.1001 BTC
TransactionOutput msOutput = new TransactionOutput(params, depositTx, inputAmount, multiSigOutputScript.getProgram()); IN[...] optional additional taker inputs (normally never used as we pay from trade fee tx and always have 1 output there)
depositTx.addOutput(msOutput); 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
*/
// Not lets find the inputs to satisfy that output and add an optional change output // 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.
addAvailableInputsAndChangeOutputs(depositTx, addressInfo); Transaction dummyTx = new Transaction(params);
Coin dummyOutputAmount = takerInputAmount.subtract(FeePolicy.TX_FEE);
// Now as we have the takers inputs and outputs we replace the temporary output amount with the real msOutputAmount TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params));
msOutput.setValue(msOutputAmount); dummyTx.addOutput(dummyOutput);
addAvailableInputsAndChangeOutputs(dummyTx, addressInfo);
// Save reference to inputs for signing, before we add offerer inputs List<TransactionInput> takerInputs = dummyTx.getInputs();
List<TransactionInput> takerInputs = new ArrayList<>(depositTx.getInputs());
List<TransactionOutput> connectedOutputsForAllTakerInputs = new ArrayList<>();
for (TransactionInput input : takerInputs) {
connectedOutputsForAllTakerInputs.add(input.getConnectedOutput());
}
// Lets save the takerOutputs for passing later to the result, the MS output is ignored
List<TransactionOutput> takerOutputs = new ArrayList<>(); List<TransactionOutput> takerOutputs = new ArrayList<>();
for (TransactionOutput output : depositTx.getOutputs()) { // we store optional change outputs (ignoring dummyOutput)
if (output.equals(msOutput)) for (int i = 1; i < dummyTx.getOutputs().size(); i++) {
continue; takerOutputs.add(dummyTx.getOutput(i));
takerOutputs.add(output);
} }
// Add all inputs from offerer (normally its just 1 input) // Now we construct real deposit tx
Transaction depositTx = new Transaction(params);
// Add offerer inputs (normally its just 1 input)
for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) {
TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
TransactionInput transactionInput = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); TransactionInput transactionInput = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue());
depositTx.addInput(transactionInput); depositTx.addInput(transactionInput);
} }
// Add optional outputs // Add taker inputs
List<TransactionOutput> 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) { for (TransactionOutput output : offererOutputs) {
depositTx.addOutput(output); depositTx.addOutput(output);
} }
printInputs("depositTx", depositTx); Coin takersSpendingAmount = Coin.ZERO;
log.debug("depositTx = " + depositTx);
// Sign taker inputs // Add optional taker outputs
// Taker inputs are the first inputs (0 -n), so the index of takerInputs and depositTx.getInputs() matches for the number of takerInputs. for (TransactionOutput output : takerOutputs) {
int index = 0; depositTx.addOutput(output);
for (TransactionInput input : takerInputs) {
log.debug("signInput input " + input.toString()); // subtract change amount
log.debug("signInput index " + index); takersSpendingAmount = takersSpendingAmount.subtract(output.getValue());
signInput(depositTx, input, index);
checkScriptSig(depositTx, input, index);
index++;
} }
// 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); verifyTransaction(depositTx);
checkWalletConsistency(); checkWalletConsistency();
printInputs("depositTx", depositTx); printTxWithInputs("depositTx", depositTx);
log.debug("depositTx = " + depositTx);
return new TransactionDataResult(depositTx, connectedOutputsForAllTakerInputs, takerOutputs); return new TransactionDataResult(depositTx, connectedOutputsForAllTakerInputs, takerOutputs);
} }
@ -735,72 +756,79 @@ public class WalletService {
// 3. step: deposit tx // 3. step: deposit tx
// Offerer signs tx and publishes it // Offerer signs tx and publishes it
public void offererSignAndPublishTx(Transaction takersDepositTx, public void offererSignAndPublishTx(Transaction takersDepositTx,
List<TransactionOutput> takersConnectedOutputsForAllInputs,
List<TransactionOutput> offererConnectedOutputsForAllInputs, List<TransactionOutput> offererConnectedOutputsForAllInputs,
List<TransactionOutput> takerConnectedOutputsForAllInputs,
List<TransactionOutput> offererOutputs,
Coin offererInputAmount,
byte[] offererPubKey, byte[] offererPubKey,
byte[] takerPubKey, byte[] takerPubKey,
byte[] arbitratorPubKey, byte[] arbitratorPubKey,
FutureCallback<Transaction> callback) throws SigningException, TransactionVerificationException, WalletException { FutureCallback<Transaction> callback) throws SigningException, TransactionVerificationException, WalletException {
// TODO verify amounts, addresses, MS 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 // 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); Transaction depositTx = new Transaction(params);
// We save offererInputs for later signing when tx is fully constructed // Add offerer inputs
List<TransactionInput> offererInputs = new ArrayList<>(); Coin offererSpendingAmount = Coin.ZERO;
// Add all inputs from offerer (normally its just 1 input)
for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) { for (TransactionOutput connectedOutputForInput : offererConnectedOutputsForAllInputs) {
TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
TransactionInput input = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue()); TransactionInput input = new TransactionInput(params, depositTx, new byte[]{}, outPoint, connectedOutputForInput.getValue());
offererInputs.add(input);
depositTx.addInput(input); depositTx.addInput(input);
// add up spending amount
offererSpendingAmount = offererSpendingAmount.add(input.getConnectedOutput().getValue());
} }
// Add all inputs from taker and apply signature // Add taker inputs and apply signature
for (TransactionOutput connectedOutputForInput : takersConnectedOutputsForAllInputs) { List<TransactionInput> takerInputs = new ArrayList<>();
for (TransactionOutput connectedOutputForInput : takerConnectedOutputsForAllInputs) {
TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction()); TransactionOutPoint outPoint = new TransactionOutPoint(params, connectedOutputForInput.getIndex(), connectedOutputForInput.getParentTransaction());
// We grab the signatures from the takersDepositTx and apply it to the new tx input // We grab the signature from the takersDepositTx and apply it to the new tx input
Optional<TransactionInput> result = takersDepositTx.getInputs().stream() TransactionInput takerInput = takersDepositTx.getInputs().get(offererConnectedOutputsForAllInputs.size());
.filter(e -> e.getConnectedOutput().hashCode() == connectedOutputForInput.hashCode()).findAny(); byte[] scriptProgram = takerInput.getScriptSig().getProgram();
if (result.isPresent()) { if (scriptProgram.length == 0)
TransactionInput signedInput = result.get(); throw new TransactionVerificationException("Inputs from taker not singed.");
Script script = signedInput.getScriptSig();
TransactionInput transactionInput = new TransactionInput(params, depositTx, script.getProgram(), outPoint, connectedOutputForInput.getValue()); TransactionInput transactionInput = new TransactionInput(params, depositTx, scriptProgram, outPoint, connectedOutputForInput.getValue());
takerInputs.add(transactionInput);
depositTx.addInput(transactionInput); depositTx.addInput(transactionInput);
} }
}
// Add all outputs from takersDepositTx to depositTx // Add all outputs from takersDepositTx to depositTx
takersDepositTx.getOutputs().forEach(depositTx::addOutput); for (TransactionOutput output : takersDepositTx.getOutputs()) {
depositTx.addOutput(output);
printInputs("depositTx", depositTx);
log.debug("depositTx = " + depositTx);
// 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;
for (TransactionInput input : offererInputs) {
signInput(depositTx, input, index);
checkScriptSig(depositTx, input, index);
index++;
} }
// TODO verify MS, amounts // Sign inputs
//Script multiSigOutputScript = getMultiSigOutputScript(offererPubKey, takerPubKey, arbitratorPubKey); 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); verifyTransaction(depositTx);
checkWalletConsistency(); checkWalletConsistency();
//checkScriptSigForAllInputs(depositTx);
// Broadcast depositTx // Broadcast depositTx
log.trace("Wallet balance before broadcastTransaction: " + wallet.getBalance()); printTxWithInputs("depositTx", depositTx);
log.trace("Check if wallet is consistent before broadcastTransaction: result=" + wallet.isConsistent());
ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(depositTx); ListenableFuture<Transaction> broadcastComplete = walletAppKit.peerGroup().broadcastTransaction(depositTx);
log.trace("Wallet balance after broadcastTransaction: " + wallet.getBalance());
log.trace("Check if wallet is consistent after broadcastTransaction: result=" + wallet.isConsistent());
Futures.addCallback(broadcastComplete, callback); Futures.addCallback(broadcastComplete, callback);
} }
@ -914,7 +942,7 @@ public class WalletService {
log.trace("getTransactions.size=" + wallet.getTransactions(true).size()); log.trace("getTransactions.size=" + wallet.getTransactions(true).size());
log.trace("Check if wallet is consistent: result=" + wallet.isConsistent()); log.trace("Check if wallet is consistent: result=" + wallet.isConsistent());
printInputs("takerSignsAndSendsTx", tx); printTxWithInputs("takerSignsAndSendsTx", tx);
log.debug("tx = " + tx); log.debug("tx = " + tx);
} }
@ -962,15 +990,13 @@ public class WalletService {
return tx; return tx;
} }
public static void printInputs(String tracePrefix, Transaction tx) { public static void printTxWithInputs(String tracePrefix, Transaction tx) {
log.trace(tracePrefix + ": " + tx.toString());
for (TransactionInput input : tx.getInputs()) { for (TransactionInput input : tx.getInputs()) {
if (input.getConnectedOutput() != null) { if (input.getConnectedOutput() != null)
log.trace(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString()); log.trace(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString());
} else
else { log.trace(tracePrefix + ": Transaction already has inputs but we don't have the connected outputs, so we don't know the value.");
log.trace(tracePrefix + ": " + "Transaction already has inputs but we don't have the connected " +
"outputs, so we don't know the value.");
}
} }
} }
@ -1038,7 +1064,8 @@ public class WalletService {
} }
} }
private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws InsufficientMoneyException { private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws WalletException {
try {
// Lets let the framework do the work to find the right inputs // Lets let the framework do the work to find the right inputs
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction);
sendRequest.shuffleOutputs = false; sendRequest.shuffleOutputs = false;
@ -1049,9 +1076,9 @@ public class WalletService {
// We don't commit that tx to the wallet as it will be changed later and it's not signed yet. // 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. // So it will not change the wallet balance.
wallet.completeTx(sendRequest); wallet.completeTx(sendRequest);
} catch (Throwable t) {
printInputs("transaction", transaction); throw new WalletException(t);
log.trace("transaction=" + transaction); }
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -26,4 +26,8 @@ public class TransactionVerificationException extends Exception {
public TransactionVerificationException(Throwable t) { public TransactionVerificationException(Throwable t) {
super(t); super(t);
} }
public TransactionVerificationException(String errorMessage) {
super(errorMessage);
}
} }

View file

@ -140,6 +140,10 @@ class TakeOfferDataModel implements Activatable, DataModel {
final Trade trade = tradeManager.takeOffer(amountAsCoin.get(), offer); final Trade trade = tradeManager.takeOffer(amountAsCoin.get(), offer);
trade.stateProperty().addListener((ov, oldValue, newValue) -> { trade.stateProperty().addListener((ov, oldValue, newValue) -> {
log.debug("trade state = " + newValue); log.debug("trade state = " + newValue);
String errorMessage = "";
if (newValue.getErrorMessage() != null)
errorMessage = "\nError message: " + newValue.getErrorMessage();
switch (newValue) { switch (newValue) {
case OPEN: case OPEN:
break; break;
@ -166,14 +170,19 @@ class TakeOfferDataModel implements Activatable, DataModel {
case FIAT_PAYMENT_STARTED: case FIAT_PAYMENT_STARTED:
break; break;
case TAKE_OFFER_FEE_PAYMENT_FAILED: case TAKE_OFFER_FEE_PAYMENT_FAILED:
requestTakeOfferErrorMessage.set("An error occurred when paying the trade fee."); requestTakeOfferErrorMessage.set("An error occurred when paying the trade fee." + errorMessage);
break; break;
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
requestTakeOfferErrorMessage.set("An error occurred when sending a message to the offerer. Maybe there are connection problems. " + requestTakeOfferErrorMessage.set("An error occurred when sending a message to the offerer. Maybe there are connection problems. " +
"Please try later again."); "Please try later again." + errorMessage);
break; break;
case PAYOUT_PUBLISHED: case PAYOUT_PUBLISHED:
break; break;
case FAULT:
requestTakeOfferErrorMessage.set(errorMessage);
break;
default: default:
log.error("Unhandled trade state: " + newValue); log.error("Unhandled trade state: " + newValue);
break; break;

View file

@ -48,7 +48,18 @@ public class Trade implements Serializable {
FIAT_PAYMENT_STARTED, FIAT_PAYMENT_STARTED,
FIAT_PAYMENT_RECEIVED, FIAT_PAYMENT_RECEIVED,
PAYOUT_PUBLISHED, PAYOUT_PUBLISHED,
MESSAGE_SENDING_FAILED MESSAGE_SENDING_FAILED,
FAULT;
private String errorMessage;
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
} }
private final Offer offer; private final Offer offer;

View file

@ -223,6 +223,7 @@ public class TradeManager {
break; break;
case OFFERER_REJECTED: case OFFERER_REJECTED:
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
case FAULT:
removeFailedTrade(trade); removeFailedTrade(trade);
break; break;
default: default:
@ -397,6 +398,7 @@ public class TradeManager {
case OFFERER_REJECTED: case OFFERER_REJECTED:
case TAKE_OFFER_FEE_PAYMENT_FAILED: case TAKE_OFFER_FEE_PAYMENT_FAILED:
case MESSAGE_SENDING_FAILED: case MESSAGE_SENDING_FAILED:
case FAULT:
removeFailedTrade(trade); removeFailedTrade(trade);
buyerAcceptsOfferProtocolMap.get(trade.getId()).cleanup(); buyerAcceptsOfferProtocolMap.get(trade.getId()).cleanup();
break; break;

View file

@ -46,8 +46,8 @@ public class ProcessRequestOffererPublishDepositTxMessage extends Task<BuyerAsOf
model.setTakerMessagePublicKey(checkNotNull(message.getTakerMessagePublicKey())); model.setTakerMessagePublicKey(checkNotNull(message.getTakerMessagePublicKey()));
model.setTakerContractAsJson(nonEmptyStringOf(message.getTakerContractAsJson())); model.setTakerContractAsJson(nonEmptyStringOf(message.getTakerContractAsJson()));
model.setTakerDepositTx(checkNotNull(message.getTakersDepositTx())); model.setTakerDepositTx(checkNotNull(message.getTakersDepositTx()));
model.setTakerConnectedOutputsForAllInputs(checkNotNull(message.getTakersConnectedOutputsForAllInputs())); model.setTakerConnectedOutputsForAllInputs(checkNotNull(message.getTakerConnectedOutputsForAllInputs()));
checkArgument(message.getTakersConnectedOutputsForAllInputs().size() > 0); checkArgument(message.getTakerConnectedOutputsForAllInputs().size() > 0);
model.setTakerOutputs(checkNotNull(message.getTakerOutputs())); model.setTakerOutputs(checkNotNull(message.getTakerOutputs()));
complete(); complete();

View file

@ -17,11 +17,13 @@
package io.bitsquare.trade.protocol.trade.offerer.tasks; package io.bitsquare.trade.protocol.trade.offerer.tasks;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
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.Transaction; import org.bitcoinj.core.Transaction;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
@ -41,10 +43,13 @@ public class SignAndPublishDepositTx extends Task<BuyerAsOffererModel> {
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
Coin offererInputAmount = model.getTrade().getSecurityDeposit().add(FeePolicy.TX_FEE);
model.getWalletService().offererSignAndPublishTx( model.getWalletService().offererSignAndPublishTx(
model.getTakerDepositTx(), model.getTakerDepositTx(),
model.getTakerConnectedOutputsForAllInputs(),
model.getOffererConnectedOutputsForAllInputs(), model.getOffererConnectedOutputsForAllInputs(),
model.getTakerConnectedOutputsForAllInputs(),
model.getOffererOutputs(),
offererInputAmount,
model.getOffererPubKey(), model.getOffererPubKey(),
model.getTakerPubKey(), model.getTakerPubKey(),
model.getArbitratorPubKey(), model.getArbitratorPubKey(),

View file

@ -38,7 +38,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
private final String contractAsJson; private final String contractAsJson;
private final String takerContractSignature; private final String takerContractSignature;
private Transaction takersDepositTx; private Transaction takersDepositTx;
private List<TransactionOutput> takersConnectedOutputsForAllInputs; private List<TransactionOutput> takerConnectedOutputsForAllInputs;
private List<TransactionOutput> takerOutputs; private List<TransactionOutput> takerOutputs;
public RequestOffererPublishDepositTxMessage(String tradeId, public RequestOffererPublishDepositTxMessage(String tradeId,
@ -48,7 +48,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
String contractAsJson, String contractAsJson,
String takerContractSignature, String takerContractSignature,
Transaction takersDepositTx, Transaction takersDepositTx,
List<TransactionOutput> takersConnectedOutputsForAllInputs, List<TransactionOutput> takerConnectedOutputsForAllInputs,
List<TransactionOutput> takerOutputs) { List<TransactionOutput> takerOutputs) {
this.tradeId = tradeId; this.tradeId = tradeId;
this.bankAccount = bankAccount; this.bankAccount = bankAccount;
@ -57,7 +57,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
this.contractAsJson = contractAsJson; this.contractAsJson = contractAsJson;
this.takerContractSignature = takerContractSignature; this.takerContractSignature = takerContractSignature;
this.takersDepositTx = takersDepositTx; this.takersDepositTx = takersDepositTx;
this.takersConnectedOutputsForAllInputs = takersConnectedOutputsForAllInputs; this.takerConnectedOutputsForAllInputs = takerConnectedOutputsForAllInputs;
this.takerOutputs = takerOutputs; this.takerOutputs = takerOutputs;
} }
@ -107,7 +107,7 @@ public class RequestOffererPublishDepositTxMessage implements Serializable, Trad
return takersDepositTx; return takersDepositTx;
} }
public List<TransactionOutput> getTakersConnectedOutputsForAllInputs() { public List<TransactionOutput> getTakerConnectedOutputsForAllInputs() {
return takersConnectedOutputsForAllInputs; return takerConnectedOutputsForAllInputs;
} }
} }

View file

@ -19,6 +19,7 @@ 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.btc.WalletService;
import io.bitsquare.trade.Trade;
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;
@ -38,11 +39,11 @@ public class TakerCreatesAndSignsDepositTx extends Task<SellerAsTakerModel> {
@Override @Override
protected void doRun() { protected void doRun() {
try { try {
Coin inputAmount = model.getTrade().getTradeAmount().add(model.getTrade().getSecurityDeposit()); Coin takerInputAmount = model.getTrade().getTradeAmount().add(model.getTrade().getSecurityDeposit()).add(FeePolicy.TX_FEE);
Coin msOutputAmount = inputAmount.add(model.getTrade().getSecurityDeposit()).add(FeePolicy.TX_FEE); Coin msOutputAmount = takerInputAmount.add(model.getTrade().getSecurityDeposit());
WalletService.TransactionDataResult result = model.getWalletService().takerCreatesAndSignsDepositTx( WalletService.TransactionDataResult result = model.getWalletService().takerCreatesAndSignsDepositTx(
inputAmount, takerInputAmount,
msOutputAmount, msOutputAmount,
model.getOffererConnectedOutputsForAllInputs(), model.getOffererConnectedOutputsForAllInputs(),
model.getOffererOutputs(), model.getOffererOutputs(),
@ -64,5 +65,8 @@ public class TakerCreatesAndSignsDepositTx extends Task<SellerAsTakerModel> {
@Override @Override
protected void updateStateOnFault() { protected void updateStateOnFault() {
Trade.State state = Trade.State.FAULT;
state.setErrorMessage(errorMessage);
model.getTrade().setState(state);
} }
} }

View file

@ -68,7 +68,10 @@ public abstract class Task<T extends SharedModel> {
} }
protected void appendExceptionToErrorMessage(Throwable t) { protected void appendExceptionToErrorMessage(Throwable t) {
if (t.getMessage() != null)
errorMessage += "\nException message: " + t.getMessage(); errorMessage += "\nException message: " + t.getMessage();
else
errorMessage += "\nException: " + t.toString();
} }
protected void complete() { protected void complete() {