From 582999844f6989af405d8fd264ac56b67710ad79 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Fri, 15 Jan 2016 11:52:27 +0100 Subject: [PATCH] add support for lost deposit tx --- .../arbitrator/ArbitratorDisputeView.java | 11 +- .../disputes/trader/DisputeSummaryPopup.java | 2 +- .../pendingtrades/PendingTradesDataModel.java | 120 ++++++++++++------ .../gui/popups/SelectDepositTxPopup.java | 107 ++++++++++++++++ 4 files changed, 193 insertions(+), 47 deletions(-) create mode 100644 gui/src/main/java/io/bitsquare/gui/popups/SelectDepositTxPopup.java diff --git a/gui/src/main/java/io/bitsquare/gui/main/disputes/arbitrator/ArbitratorDisputeView.java b/gui/src/main/java/io/bitsquare/gui/main/disputes/arbitrator/ArbitratorDisputeView.java index d00dd430b3..b22d7c42a5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/disputes/arbitrator/ArbitratorDisputeView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/disputes/arbitrator/ArbitratorDisputeView.java @@ -19,10 +19,7 @@ package io.bitsquare.gui.main.disputes.arbitrator; import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.DisputeManager; -import io.bitsquare.btc.TradeWalletService; -import io.bitsquare.btc.WalletService; import io.bitsquare.common.crypto.KeyRing; -import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.main.disputes.trader.DisputeSummaryPopup; import io.bitsquare.gui.main.disputes.trader.TraderDisputeView; @@ -40,10 +37,10 @@ import javax.inject.Inject; public class ArbitratorDisputeView extends TraderDisputeView { @Inject - public ArbitratorDisputeView(DisputeManager disputeManager, KeyRing keyRing, TradeWalletService tradeWalletService, WalletService walletService, - TradeManager tradeManager, Stage stage, BSFormatter formatter, Navigation navigation, - DisputeSummaryPopup disputeSummaryPopup, ContractPopup contractPopup, TradeDetailsPopup tradeDetailsPopup) { - super(disputeManager, keyRing, tradeWalletService, walletService, tradeManager, stage, formatter, navigation, + public ArbitratorDisputeView(DisputeManager disputeManager, KeyRing keyRing, TradeManager tradeManager, Stage stage, + BSFormatter formatter, DisputeSummaryPopup disputeSummaryPopup, + ContractPopup contractPopup, TradeDetailsPopup tradeDetailsPopup) { + super(disputeManager, keyRing, tradeManager, stage, formatter, disputeSummaryPopup, contractPopup, tradeDetailsPopup); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java index 3a0ec84c63..ce563fe244 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java +++ b/gui/src/main/java/io/bitsquare/gui/main/disputes/trader/DisputeSummaryPopup.java @@ -381,7 +381,7 @@ public class DisputeSummaryPopup extends Popup { e2.printStackTrace(); } } else { - log.warn("dispute.getDepositTxOptional is empty"); + log.warn("dispute.getDepositTxSerialized is null"); } }); diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java index fe2078b856..61e84fb821 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesDataModel.java @@ -18,6 +18,7 @@ package io.bitsquare.gui.main.portfolio.pendingtrades; import com.google.inject.Inject; +import io.bitsquare.app.Log; import io.bitsquare.arbitration.Dispute; import io.bitsquare.arbitration.DisputeManager; import io.bitsquare.btc.FeePolicy; @@ -32,6 +33,7 @@ import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.main.portfolio.PortfolioView; import io.bitsquare.gui.main.portfolio.closedtrades.ClosedTradesView; import io.bitsquare.gui.popups.Popup; +import io.bitsquare.gui.popups.SelectDepositTxPopup; import io.bitsquare.gui.popups.WalletPasswordPopup; import io.bitsquare.payment.PaymentAccountContractData; import io.bitsquare.trade.*; @@ -45,8 +47,11 @@ import javafx.collections.ObservableList; import org.bitcoinj.core.BlockChainListener; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.TransactionOutput; import org.spongycastle.crypto.params.KeyParameter; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -182,57 +187,94 @@ public class PendingTradesDataModel extends ActivatableDataModel { } public void onOpenDispute() { - doOpenDispute(false); + tryOpenDispute(false); } public void onOpenSupportTicket() { - doOpenDispute(true); + tryOpenDispute(true); } - private void doOpenDispute(boolean isSupportTicket) { + private void tryOpenDispute(boolean isSupportTicket) { if (trade != null) { Transaction depositTx = trade.getDepositTx(); - log.debug("trade.getDepositTx() " + depositTx); - byte[] depositTxSerialized = null; - byte[] payoutTxSerialized = null; - String depositTxHashAsString = null; - String payoutTxHashAsString = null; if (depositTx != null) { - depositTxSerialized = depositTx.bitcoinSerialize(); - depositTxHashAsString = depositTx.getHashAsString(); - } - Transaction payoutTx = trade.getPayoutTx(); - if (payoutTx != null) { - payoutTxSerialized = payoutTx.bitcoinSerialize(); - payoutTxHashAsString = payoutTx.getHashAsString(); - } + doOpenDispute(isSupportTicket, trade.getDepositTx()); + } else { + log.warn("Trade.depositTx is null. We try to find the tx in our wallet."); + List candidates = new ArrayList<>(); + List transactions = walletService.getWallet().getRecentTransactions(100, true); + transactions.stream().forEach(transaction -> { + Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet()); + if (!valueSentFromMe.isZero()) { + // spending tx + for (TransactionOutput transactionOutput : transaction.getOutputs()) { + if (!transactionOutput.isMine(walletService.getWallet())) { + if (transactionOutput.getScriptPubKey().isPayToScriptHash()) { + // MS tx + candidates.add(transaction); + } + } + } + } + }); - Dispute dispute = new Dispute(disputeManager.getDisputeStorage(), - trade.getId(), - keyRing.getPubKeyRing().hashCode(), // traderId - trade.getOffer().getDirection() == Offer.Direction.BUY ? isOfferer : !isOfferer, - isOfferer, - keyRing.getPubKeyRing(), - trade.getDate(), - trade.getContract(), - trade.getContractHash(), - depositTxSerialized, - payoutTxSerialized, - depositTxHashAsString, - payoutTxHashAsString, - trade.getContractAsJson(), - trade.getOffererContractSignature(), - trade.getTakerContractSignature(), - user.getAcceptedArbitratorByAddress(trade.getArbitratorAddress()).getPubKeyRing(), - isSupportTicket - ); - - trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED); - disputeManager.sendOpenNewDisputeMessage(dispute); - navigation.navigateTo(MainView.class, DisputesView.class); + if (candidates.size() == 1) + doOpenDispute(isSupportTicket, candidates.get(0)); + else if (candidates.size() > 1) + new SelectDepositTxPopup().transactions(candidates).onSelect(transaction -> { + doOpenDispute(isSupportTicket, transaction); + }).show(); + else + log.error("Trade.depositTx is null and we did not find any MultiSig transaction."); + } + } else { + log.error("Trade is null"); } } + private void doOpenDispute(boolean isSupportTicket, Transaction depositTx) { + Log.traceCall("depositTx=" + depositTx); + byte[] depositTxSerialized = null; + byte[] payoutTxSerialized = null; + String depositTxHashAsString = null; + String payoutTxHashAsString = null; + if (depositTx != null) { + depositTxSerialized = depositTx.bitcoinSerialize(); + depositTxHashAsString = depositTx.getHashAsString(); + } else { + log.warn("depositTx is null"); + } + Transaction payoutTx = trade.getPayoutTx(); + if (payoutTx != null) { + payoutTxSerialized = payoutTx.bitcoinSerialize(); + payoutTxHashAsString = payoutTx.getHashAsString(); + } + + Dispute dispute = new Dispute(disputeManager.getDisputeStorage(), + trade.getId(), + keyRing.getPubKeyRing().hashCode(), // traderId + trade.getOffer().getDirection() == Offer.Direction.BUY ? isOfferer : !isOfferer, + isOfferer, + keyRing.getPubKeyRing(), + trade.getDate(), + trade.getContract(), + trade.getContractHash(), + depositTxSerialized, + payoutTxSerialized, + depositTxHashAsString, + payoutTxHashAsString, + trade.getContractAsJson(), + trade.getOffererContractSignature(), + trade.getTakerContractSignature(), + user.getAcceptedArbitratorByAddress(trade.getArbitratorAddress()).getPubKeyRing(), + isSupportTicket + ); + + trade.setDisputeState(Trade.DisputeState.DISPUTE_REQUESTED); + disputeManager.sendOpenNewDisputeMessage(dispute); + navigation.navigateTo(MainView.class, DisputesView.class); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getters /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/src/main/java/io/bitsquare/gui/popups/SelectDepositTxPopup.java b/gui/src/main/java/io/bitsquare/gui/popups/SelectDepositTxPopup.java new file mode 100644 index 0000000000..958253cb0a --- /dev/null +++ b/gui/src/main/java/io/bitsquare/gui/popups/SelectDepositTxPopup.java @@ -0,0 +1,107 @@ +/* + * This file is part of Bitsquare. + * + * Bitsquare is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bitsquare is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bitsquare. If not, see . + */ + +package io.bitsquare.gui.popups; + +import io.bitsquare.common.util.Tuple2; +import javafx.collections.FXCollections; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.util.StringConverter; +import org.bitcoinj.core.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import static io.bitsquare.gui.util.FormBuilder.addLabelComboBox; +import static io.bitsquare.gui.util.FormBuilder.addMultilineLabel; + +public class SelectDepositTxPopup extends Popup { + private static final Logger log = LoggerFactory.getLogger(SelectDepositTxPopup.class); + private Button emptyWalletButton; + private ComboBox transactionsComboBox; + private List transaction; + private Optional> selectHandlerOptional; + + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////////////////////// + + @Inject + public SelectDepositTxPopup() { + } + + public SelectDepositTxPopup show() { + if (headLine == null) + headLine = "Select deposit transaction for dispute"; + + width = 700; + createGridPane(); + addHeadLine(); + addContent(); + createPopup(); + return this; + } + + public SelectDepositTxPopup onSelect(Consumer selectHandler) { + this.selectHandlerOptional = Optional.of(selectHandler); + return this; + } + + public SelectDepositTxPopup transactions(List transaction) { + this.transaction = transaction; + return this; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Protected + /////////////////////////////////////////////////////////////////////////////////////////// + + private void addContent() { + addMultilineLabel(gridPane, ++rowIndex, + "The deposit transaction was not stored in the trade.\n" + + "Please select one of the existing MultiSig transactions from your wallet which was the " + + "deposit transaction used in the failed trade.", + 10); + + Tuple2 tuple = addLabelComboBox(gridPane, ++rowIndex); + transactionsComboBox = tuple.second; + transactionsComboBox.setConverter(new StringConverter() { + @Override + public String toString(Transaction transaction) { + return transaction.getHashAsString(); + } + + @Override + public Transaction fromString(String string) { + return null; + } + }); + transactionsComboBox.setItems(FXCollections.observableArrayList(transaction)); + transactionsComboBox.setOnAction(event -> { + selectHandlerOptional.get().accept(transactionsComboBox.getSelectionModel().getSelectedItem()); + }); + } + + +}