mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 18:03:12 +01:00
Reattach addresses when unfailing trade
This commit is contained in:
parent
bd8e30c708
commit
817819dc51
@ -598,6 +598,13 @@ public class BtcWalletService extends WalletService {
|
||||
return entry;
|
||||
}
|
||||
|
||||
public AddressEntry recoverAddressEntry(String offerId, String address, AddressEntry.Context context) {
|
||||
var available = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
|
||||
if (!available.isPresent())
|
||||
return null;
|
||||
return addressEntryList.swapAvailableToAddressEntryWithOfferId(available.get(), context, offerId);
|
||||
}
|
||||
|
||||
private AddressEntry getOrCreateAddressEntry(AddressEntry.Context context, Optional<AddressEntry> addressEntry) {
|
||||
if (addressEntry.isPresent()) {
|
||||
return addressEntry.get();
|
||||
|
@ -64,6 +64,8 @@ import bisq.common.handlers.ResultHandler;
|
||||
import bisq.common.proto.network.NetworkEnvelope;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.storage.Storage;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import org.bitcoinj.core.AddressFormatException;
|
||||
import org.bitcoinj.core.Coin;
|
||||
@ -89,6 +91,7 @@ import org.spongycastle.crypto.params.KeyParameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@ -604,12 +607,35 @@ public class TradeManager implements PersistedDataHost {
|
||||
}
|
||||
|
||||
// If trade still has funds locked up it might come back from failed trades
|
||||
private void unfailTrade(Trade trade) {
|
||||
// Aborts unfailing if the address entries needed are not available
|
||||
private boolean unfailTrade(Trade trade) {
|
||||
if (!recoverAddresses(trade)) {
|
||||
log.warn("Failed to recover address during unfail trade");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tradableList.contains(trade)) {
|
||||
tradableList.add(trade);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// The trade is added to pending trades if the associated address entries are AVAILABLE and
|
||||
// the relevant entries are changed, otherwise it's not added and no address entries are changed
|
||||
private boolean recoverAddresses(Trade trade) {
|
||||
// Find addresses associated with this trade.
|
||||
var entries = TradeUtils.getAvailableAddresses(trade, btcWalletService, keyRing);
|
||||
if (entries == null)
|
||||
return false;
|
||||
|
||||
btcWalletService.recoverAddressEntry(trade.getId(), entries.first,
|
||||
AddressEntry.Context.MULTI_SIG);
|
||||
btcWalletService.recoverAddressEntry(trade.getId(), entries.second,
|
||||
AddressEntry.Context.TRADE_PAYOUT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// If trade is in preparation (if taker role: before taker fee is paid; both roles: before deposit published)
|
||||
// we just remove the trade from our list. We don't store those trades.
|
||||
public void removePreparedTrade(Trade trade) {
|
||||
|
79
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
79
core/src/main/java/bisq/core/trade/TradeUtils.java
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq 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.
|
||||
*
|
||||
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.trade;
|
||||
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.util.Tuple2;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class TradeUtils {
|
||||
|
||||
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
||||
static Tuple2<String, String> getAvailableAddresses(Trade trade, BtcWalletService btcWalletService,
|
||||
KeyRing keyRing) {
|
||||
var addresses = getTradeAddresses(trade, btcWalletService, keyRing);
|
||||
if (addresses == null)
|
||||
return null;
|
||||
|
||||
if (btcWalletService.getAvailableAddressEntries().stream()
|
||||
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first)))
|
||||
return null;
|
||||
if (btcWalletService.getAvailableAddressEntries().stream()
|
||||
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second)))
|
||||
return null;
|
||||
|
||||
return new Tuple2<>(addresses.first, addresses.second);
|
||||
}
|
||||
|
||||
// Returns <MULTI_SIG, TRADE_PAYOUT> addresses as strings if they're known by the wallet
|
||||
public static Tuple2<String, String> getTradeAddresses(Trade trade, BtcWalletService btcWalletService,
|
||||
KeyRing keyRing) {
|
||||
var contract = trade.getContract();
|
||||
if (contract == null)
|
||||
return null;
|
||||
|
||||
// Get multisig address
|
||||
var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
|
||||
var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
||||
if (multiSigPubKey == null)
|
||||
return null;
|
||||
var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
|
||||
var multiSigAddress = btcWalletService.getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (multiSigAddress == null)
|
||||
return null;
|
||||
|
||||
// Get payout address
|
||||
var payoutAddress = isMyRoleBuyer ?
|
||||
contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString();
|
||||
var payoutAddressEntry = btcWalletService.getAddressEntryListAsImmutableList().stream()
|
||||
.filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (payoutAddressEntry == null)
|
||||
return null;
|
||||
|
||||
return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
|
||||
}
|
||||
}
|
@ -17,12 +17,14 @@
|
||||
|
||||
package bisq.core.trade.failed;
|
||||
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
import bisq.core.btc.wallet.BtcWalletService;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.DumpDelayedPayoutTx;
|
||||
import bisq.core.trade.TradableList;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeUtils;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
@ -33,7 +35,7 @@ import com.google.inject.Inject;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -50,7 +52,7 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
private final Storage<TradableList<Trade>> tradableListStorage;
|
||||
private final DumpDelayedPayoutTx dumpDelayedPayoutTx;
|
||||
@Setter
|
||||
private Consumer<Trade> unfailTradeCallback;
|
||||
private Function<Trade, Boolean> unfailTradeCallback;
|
||||
|
||||
@Inject
|
||||
public FailedTradesManager(KeyRing keyRing,
|
||||
@ -103,8 +105,29 @@ public class FailedTradesManager implements PersistedDataHost {
|
||||
}
|
||||
|
||||
public void unfailTrade(Trade trade) {
|
||||
if (unfailTradeCallback == null) return;
|
||||
unfailTradeCallback.accept(trade);
|
||||
failedTrades.remove(trade);
|
||||
if (unfailTradeCallback == null)
|
||||
return;
|
||||
|
||||
if (unfailTradeCallback.apply(trade)) {
|
||||
failedTrades.remove(trade);
|
||||
}
|
||||
}
|
||||
|
||||
public String checkUnfail(Trade trade) {
|
||||
var addresses = TradeUtils.getTradeAddresses(trade, btcWalletService, keyRing);
|
||||
if (addresses == null) {
|
||||
return "Addresses not found";
|
||||
}
|
||||
StringBuilder blockingTrades = new StringBuilder();
|
||||
for (var entry : btcWalletService.getAddressEntryListAsImmutableList()) {
|
||||
if (entry.getContext() == AddressEntry.Context.AVAILABLE)
|
||||
continue;
|
||||
if (entry.getAddressString() != null &&
|
||||
(entry.getAddressString().equals(addresses.first) ||
|
||||
entry.getAddressString().equals(addresses.second))) {
|
||||
blockingTrades.append(entry.getOfferId()).append(", ");
|
||||
}
|
||||
}
|
||||
return blockingTrades.toString();
|
||||
}
|
||||
}
|
||||
|
@ -859,8 +859,11 @@ portfolio.closed.ticketClosed=Arbitrated
|
||||
portfolio.closed.mediationTicketClosed=Mediated
|
||||
portfolio.closed.canceled=Canceled
|
||||
portfolio.failed.Failed=Failed
|
||||
portfolio.failed.unfail=Do you want to move this trade back to pending trades? \
|
||||
Only do this if you need to open a support ticket.
|
||||
portfolio.failed.unfail=Before proceeding, make sure you have a backup of your data directory!\n\
|
||||
Do you want to move this trade back to open trades?\n\
|
||||
This is a way to unlock funds stuck in a failed trade.
|
||||
portfolio.failed.cantUnfail=This trade cannot be moved back to open trades at the moment. \n\
|
||||
Try again after completion of trade(s) {0}
|
||||
|
||||
|
||||
####################################################################
|
||||
|
@ -77,4 +77,8 @@ class FailedTradesDataModel extends ActivatableDataModel {
|
||||
public void unfailTrade(Trade trade) {
|
||||
failedTradesManager.unfailTrade(trade);
|
||||
}
|
||||
|
||||
public String checkUnfail(Trade trade) {
|
||||
return failedTradesManager.checkUnfail(trade);
|
||||
}
|
||||
}
|
||||
|
@ -108,9 +108,16 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
||||
|
||||
keyEventEventHandler = keyEvent -> {
|
||||
if (Utilities.isAltOrCtrlPressed(KeyCode.Y, keyEvent)) {
|
||||
new Popup().warning(Res.get("portfolio.failed.unfail"))
|
||||
.onAction(this::onUnfail)
|
||||
.show();
|
||||
var checkUnfailString = checkUnfail();
|
||||
if (!checkUnfailString.isEmpty()) {
|
||||
new Popup().warning(Res.get("portfolio.failed.cantUnfail", checkUnfailString))
|
||||
.onAction(this::onUnfail)
|
||||
.show();
|
||||
} else {
|
||||
new Popup().warning(Res.get("portfolio.failed.unfail"))
|
||||
.onAction(this::onUnfail)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -119,6 +126,11 @@ public class FailedTradesView extends ActivatableViewAndModel<VBox, FailedTrades
|
||||
private void onUnfail() {
|
||||
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
|
||||
model.dataModel.unfailTrade(trade);
|
||||
}
|
||||
|
||||
private String checkUnfail() {
|
||||
Trade trade = sortedList.get(tableView.getSelectionModel().getFocusedIndex()).getTrade();
|
||||
return model.dataModel.checkUnfail(trade);
|
||||
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user