mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-07-26 00:15:18 -04:00
Fix bug with different tx versions between taker and buyer
This commit is contained in:
parent
25ff3d26f1
commit
3db41e4edf
10 changed files with 193 additions and 128 deletions
|
@ -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);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue